Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion ExtLibs/ArduPilot/Joystick/JoyButton.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace MissionPlanner.Joystick
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace MissionPlanner.Joystick
{
public struct JoyButton
{
Expand All @@ -10,6 +13,7 @@ public struct JoyButton
/// <summary>
/// Fucntion we are doing for this button press
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public buttonfunction function;

/// <summary>
Expand Down
6 changes: 5 additions & 1 deletion ExtLibs/ArduPilot/Joystick/JoyChannel.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
namespace MissionPlanner.Joystick
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace MissionPlanner.Joystick
{
public struct JoyChannel
{
public int channel;
[JsonConverter(typeof(StringEnumConverter))]
public joystickaxis axis;
public bool reverse;
public int expo;
Expand Down
63 changes: 59 additions & 4 deletions ExtLibs/ArduPilot/Joystick/JoystickBase.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System;
using log4net;
using MissionPlanner.ArduPilot;
using MissionPlanner.Utilities;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Threading;
using log4net;
using MissionPlanner.ArduPilot;
using MissionPlanner.Utilities;

namespace MissionPlanner.Joystick
{
Expand Down Expand Up @@ -1302,5 +1303,59 @@ public static JoystickBase Create(Func<MAVLinkInterface> func)
return new JoystickWindows(func);
}
}

public void ExportConfig(string fileName)
{
// compress all joystick config files from user data directory
string userDataDir = Settings.GetUserDataDirectory();

// find all joystick config files (buttons and axis)
var buttonFiles = Directory.GetFiles(userDataDir, "joystickbutton*.xml", SearchOption.TopDirectoryOnly);
var axisFiles = Directory.GetFiles(userDataDir, "joystickaxis*.xml", SearchOption.TopDirectoryOnly);

var allFiles = buttonFiles.Concat(axisFiles).ToList();

if (allFiles.Count == 0)
{
throw new FileNotFoundException("No joystick configuration files found in " + userDataDir);
}

if (File.Exists(fileName))
File.Delete(fileName);

// create archive with all config files
using (var archive = ZipFile.Open(fileName, ZipArchiveMode.Create))
{
foreach (var file in allFiles)
{
archive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
}

public void ImportConfig(string fileName)
{
// decompress all joystick config files to user data directory
string userDataDir = Settings.GetUserDataDirectory();

if (!File.Exists(fileName))
{
throw new FileNotFoundException($"Archive file not found: {fileName}");
}

// extract all files from archive to user data directory
using (var archive = ZipFile.OpenRead(fileName))
{
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
Comment on lines +1349 to +1355
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file name validation is insufficient. The method should validate that the entry name doesn't contain directory traversal sequences (like ".." or absolute paths) before extracting to prevent potential security vulnerabilities where a malicious archive could write files outside the intended directory.

Suggested change
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
// normalize base directory once for traversal checks
var fullUserDataDir = Path.GetFullPath(userDataDir);
if (!fullUserDataDir.EndsWith(Path.DirectorySeparatorChar.ToString()) &&
!fullUserDataDir.EndsWith(Path.AltDirectorySeparatorChar.ToString()))
{
fullUserDataDir += Path.DirectorySeparatorChar;
}
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
var entryName = entry.Name;
// basic validation to prevent directory traversal and invalid paths
if (string.IsNullOrEmpty(entryName))
continue;
if (Path.IsPathRooted(entryName))
continue;
if (entryName.Contains(".."))
continue;
if (entryName.Contains(Path.DirectorySeparatorChar.ToString()) ||
entryName.Contains(Path.AltDirectorySeparatorChar.ToString()))
continue;
string outputPath = Path.Combine(userDataDir, entryName);
string fullOutputPath = Path.GetFullPath(outputPath);
// ensure the resolved path is still under the intended directory
if (!fullOutputPath.StartsWith(fullUserDataDir, StringComparison.OrdinalIgnoreCase))
continue;
entry.ExtractToFile(fullOutputPath, true);

Copilot uses AI. Check for mistakes.
}
}
}
Comment on lines +1309 to +1358
Copy link

Copilot AI Jan 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ExportConfig and ImportConfig methods lack error handling for file I/O operations. If operations like File.Delete, ZipFile.Open, or ZipFile.OpenRead fail, the exceptions will propagate unhandled to the UI layer. Consider wrapping these operations in try-catch blocks and providing user-friendly error messages, or at minimum document that calling code should handle these exceptions.

Suggested change
// compress all joystick config files from user data directory
string userDataDir = Settings.GetUserDataDirectory();
// find all joystick config files (buttons and axis)
var buttonFiles = Directory.GetFiles(userDataDir, "joystickbutton*.xml", SearchOption.TopDirectoryOnly);
var axisFiles = Directory.GetFiles(userDataDir, "joystickaxis*.xml", SearchOption.TopDirectoryOnly);
var allFiles = buttonFiles.Concat(axisFiles).ToList();
if (allFiles.Count == 0)
{
throw new FileNotFoundException("No joystick configuration files found in " + userDataDir);
}
if (File.Exists(fileName))
File.Delete(fileName);
// create archive with all config files
using (var archive = ZipFile.Open(fileName, ZipArchiveMode.Create))
{
foreach (var file in allFiles)
{
archive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
}
public void ImportConfig(string fileName)
{
// decompress all joystick config files to user data directory
string userDataDir = Settings.GetUserDataDirectory();
if (!File.Exists(fileName))
{
throw new FileNotFoundException("Archive file not found: " + fileName);
}
// extract all files from archive to user data directory
using (var archive = ZipFile.OpenRead(fileName))
{
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
}
}
}
try
{
// compress all joystick config files from user data directory
string userDataDir = Settings.GetUserDataDirectory();
// find all joystick config files (buttons and axis)
var buttonFiles = Directory.GetFiles(userDataDir, "joystickbutton*.xml", SearchOption.TopDirectoryOnly);
var axisFiles = Directory.GetFiles(userDataDir, "joystickaxis*.xml", SearchOption.TopDirectoryOnly);
var allFiles = buttonFiles.Concat(axisFiles).ToList();
if (allFiles.Count == 0)
{
throw new FileNotFoundException("No joystick configuration files found in " + userDataDir);
}
if (File.Exists(fileName))
File.Delete(fileName);
// create archive with all config files
using (var archive = ZipFile.Open(fileName, ZipArchiveMode.Create))
{
foreach (var file in allFiles)
{
archive.CreateEntryFromFile(file, Path.GetFileName(file));
}
}
}
catch (FileNotFoundException)
{
// Preserve explicit FileNotFoundException messages for callers
throw;
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is InvalidDataException)
{
throw new IOException($"Failed to export joystick configuration to '{fileName}': {ex.Message}", ex);
}
}
public void ImportConfig(string fileName)
{
try
{
// decompress all joystick config files to user data directory
string userDataDir = Settings.GetUserDataDirectory();
if (!File.Exists(fileName))
{
throw new FileNotFoundException("Archive file not found: " + fileName);
}
// extract all files from archive to user data directory
using (var archive = ZipFile.OpenRead(fileName))
{
foreach (var entry in archive.Entries)
{
// only extract joystick config files
if (entry.Name.StartsWith("joystickbutton") || entry.Name.StartsWith("joystickaxis"))
{
string outputPath = Path.Combine(userDataDir, entry.Name);
entry.ExtractToFile(outputPath, true);
}
}
}
}
catch (FileNotFoundException)
{
// Preserve explicit FileNotFoundException messages for callers
throw;
}
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException || ex is InvalidDataException)
{
throw new IOException($"Failed to import joystick configuration from '{fileName}': {ex.Message}", ex);
}

Copilot uses AI. Check for mistakes.
}
}
}
2 changes: 1 addition & 1 deletion ExtLibs/SvgNet/SvgNet.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFrameworks>net472;netstandard2.0;net8.0</TargetFrameworks>
<DebugType>portable</DebugType>
<SignAssembly>true</SignAssembly>
<DelaySign>false</DelaySign>
Expand Down
124 changes: 72 additions & 52 deletions Joystick/JoystickSetup.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading