Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[xaprepare] Make 7zip work with "dangerous" symlinks in ZIPs #8737

Merged
merged 3 commits into from
Feb 21, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,78 +12,78 @@ namespace Xamarin.Android.Prepare
/// <see cref="VersionOutputLine"/> is set to any other value only that line is taken into consideration. This is
/// done to make processing less ambiguous and faster.
/// </summary>
class RegexProgramVersionParser : ProgramVersionParser
{
const string VersionGroupName = "Version";
static readonly char[] LineSeparator = new [] { '\n' };
class RegexProgramVersionParser : ProgramVersionParser
{
public const string VersionGroupName = "Version";
public static readonly char[] LineSeparator = new [] { '\n' };

Regex rx;
Regex rx;

public RegexProgramVersionParser (string programName, string versionArguments, Regex regex, uint versionOutputLine = 0, Log? log = null)
: base (programName, versionArguments, versionOutputLine, log)
{
if (regex == null)
throw new ArgumentNullException (nameof (regex));
rx = regex;
}
public RegexProgramVersionParser (string programName, string versionArguments, Regex regex, uint versionOutputLine = 0, Log? log = null)
: base (programName, versionArguments, versionOutputLine, log)
{
if (regex == null)
throw new ArgumentNullException (nameof (regex));
rx = regex;
}

public RegexProgramVersionParser (string programName, string versionArguments, string regex, uint versionOutputLine = 0, Log? log = null)
: base (programName, versionArguments, versionOutputLine, log)
{
if (String.IsNullOrEmpty (regex))
throw new ArgumentException ("must not be null or empty", nameof (regex));
public RegexProgramVersionParser (string programName, string versionArguments, string regex, uint versionOutputLine = 0, Log? log = null)
: base (programName, versionArguments, versionOutputLine, log)
{
if (String.IsNullOrEmpty (regex))
throw new ArgumentException ("must not be null or empty", nameof (regex));

rx = new Regex (regex, RegexOptions.Compiled);
}
rx = new Regex (regex, RegexOptions.Compiled);
}

protected override string ParseVersion (string programOutput)
{
string output = programOutput.Trim ();
if (String.IsNullOrEmpty (output)) {
Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty");
return DefaultVersionString;
}
protected override string ParseVersion (string programOutput)
{
string output = programOutput.Trim ();
if (String.IsNullOrEmpty (output)) {
Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty");
return DefaultVersionString;
}

string ret = String.Empty;
string[] lines = programOutput.Split (LineSeparator);
if (VersionOutputLine > 0) {
if (lines.Length < VersionOutputLine) {
Log.WarningLine ($"Not enough lines in version output of {ProgramName}: version number was supposed to be found on line {VersionOutputLine} but there are only {lines.Length} lines");
return DefaultVersionString;
}
string ret = String.Empty;
string[] lines = programOutput.Split (LineSeparator);
if (VersionOutputLine > 0) {
if (lines.Length < VersionOutputLine) {
Log.WarningLine ($"Not enough lines in version output of {ProgramName}: version number was supposed to be found on line {VersionOutputLine} but there are only {lines.Length} lines");
return DefaultVersionString;
}

if (TryMatch (lines [VersionOutputLine - 1], out ret) && !String.IsNullOrEmpty (ret)) {
return ret;
}
if (TryMatch (rx, lines [VersionOutputLine - 1], out ret) && !String.IsNullOrEmpty (ret)) {
return ret;
}

return DefaultVersionString;
}
return DefaultVersionString;
}

foreach (string line in lines) {
if (TryMatch (line, out ret))
break;
}
foreach (string line in lines) {
if (TryMatch (rx, line, out ret))
break;
}

return ret ?? DefaultVersionString;
}
return ret ?? DefaultVersionString;
}

bool TryMatch (string line, out string version)
{
version = String.Empty;
public static bool TryMatch (Regex regex, string line, out string version)
{
version = String.Empty;

Match match = rx.Match (line);
if (!match.Success || match.Groups.Count <= 0) {
return false;
}
Match match = regex.Match (line);
if (!match.Success || match.Groups.Count <= 0) {
return false;
}

foreach (Group group in match.Groups) {
if (String.Compare (group.Name, VersionGroupName, StringComparison.OrdinalIgnoreCase) == 0) {
version = group.Value;
return true;
}
}
foreach (Group group in match.Groups) {
if (String.Compare (group.Name, VersionGroupName, StringComparison.OrdinalIgnoreCase) == 0) {
version = group.Value;
return true;
}
}

return false;
}
}
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Text.RegularExpressions;

namespace Xamarin.Android.Prepare;

class SevenZipVersionParser : ProgramVersionParser
{
const string VersionArgument = "--help";
readonly Regex fallbackRegex;
readonly Regex modernRegex;

public SevenZipVersionParser (string programName, Regex fallbackRegex, Log? log = null)
: base (programName, VersionArgument, 0, log)
{
this.fallbackRegex = fallbackRegex;
modernRegex = VersionFetchers.MakeRegex (@"^7-Zip (\(a\) ){0,1}(?<Version>[\d]+\.[\d]+)");
}

protected override string ParseVersion (string programOutput)
{
string output = programOutput.Trim ();
if (String.IsNullOrEmpty (output)) {
Log.WarningLine ($"Unable to parse version of {ProgramName} because version output was empty");
return DefaultVersionString;
}

string ret = String.Empty;
string[] lines = programOutput.Split (RegexProgramVersionParser.LineSeparator);

// First try to find the official 7zip release version
foreach (string l in lines) {
string line = l.Trim ();

if (line.Length == 0) {
continue;
}

if (line.StartsWith ("7-Zip", StringComparison.OrdinalIgnoreCase)) {
// Strings of the form:
// 7-Zip 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20
// 7-Zip (a) 23.01 (x64) : Copyright (c) 1999-2023 Igor Pavlov : 2023-06-20
// 7-Zip (a) 18.01 (x64) : Copyright (c) 1999-2018 Igor Pavlov : 2018-01-28
// 7-Zip (a) 18.01 (x86) : Copyright (c) 1999-2018 Igor Pavlov : 2018-01-28
if (RegexProgramVersionParser.TryMatch (modernRegex, line, out ret)) {
return ret;
}
}

// Since we know we're dealing with `--help` option output, we can short-circuit things
if (line.StartsWith ("Usage:", StringComparison.OrdinalIgnoreCase)) {
break;
}
}

// Modern version wasn't found, try again with the fallback one
foreach (string l in lines) {
string line = l.Trim ();

if (line.Length == 0) {
continue;
}

if (RegexProgramVersionParser.TryMatch (fallbackRegex, line, out ret)) {
return ret;
}
}

return DefaultVersionString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class VersionFetchers

public Dictionary<string, ProgramVersionParser> Fetchers => GetFetchers ();

static Regex MakeRegex (string regex)
internal static Regex MakeRegex (string regex)
{
return new Regex (regex, RegexOptions.Compiled | RegexOptions.Singleline);
}
Expand All @@ -30,8 +30,11 @@ static Regex MakeRegex (string regex)
return fetchers;

fetchers = new Dictionary <string, ProgramVersionParser> (Context.Instance.OS.DefaultStringComparer) {
{"7z", "--help", MakeRegex ($"Version {StandardVersionRegex}"), 3},
{"7za", "--help", MakeRegex ($"Version {StandardVersionRegex}"), 3},
// Program-specific parsers
{"7z", new SevenZipVersionParser ("7z", MakeRegex ($"Version {StandardVersionRegex}"))},
{"7za", new SevenZipVersionParser ("7za", MakeRegex ($"Version {StandardVersionRegex}"))},

// Regex parsers
{"autoconf", "--version", StandardVersionAtEOL, 1},
{"automake", "--version", StandardVersionAtEOL, 1},
{"brew", "--version", MakeRegex ($"^Homebrew {StandardVersionRegex}"), 1},
Expand Down
17 changes: 17 additions & 0 deletions build-tools/xaprepare/xaprepare/ToolRunners/SevenZipRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ partial class SevenZipRunner : ToolRunner
{
const double DefaultTimeout = 30; // minutes
static readonly Version bsoepMinVersion = new Version (15, 5);

// Just an educated guess. The official download page had versions 19 and then 23+ available
// and the 19 one didn't support the `-snld` switch
static readonly Version snldMinVersion = new Version (20, 0);
Version version;

protected override string DefaultToolExecutableName => "7za";
Expand All @@ -36,6 +40,7 @@ public async Task<bool> Extract (string archivePath, string outputDirectory, Lis
throw new ArgumentException ("must not be null or empty", nameof (outputDirectory));

ProcessRunner runner = CreateProcessRunner ("x");

AddStandardArguments (runner);
AddArguments (runner, extraArguments);
runner.AddQuotedArgument ($"-o{outputDirectory}");
Expand Down Expand Up @@ -114,6 +119,18 @@ protected override TextWriter CreateLogSink (string? logFilePath)

void AddStandardArguments (ProcessRunner runner)
{
Log.DebugLine ($"7-zip standard arguments, for 7z version {version}");

// Ignore some "dangerous" symbolic symlinks in the ZIP archives. This allows 7zip to unpack Android NDK archives
// without error. The option appears to be undocumented, but was mentioned by the 7zip author here:
//
// https://sourceforge.net/p/sevenzip/discussion/45798/thread/187ce54fb0/
//
if (version >= snldMinVersion) {
Log.DebugLine ("Adding option to ignore dangerous symlinks");
runner.AddArgument ("-snld");
}

// Disable progress indicator (doesn't appear to have any effect with some versions of 7z)
runner.AddArgument ("-bd");

Expand Down
5 changes: 2 additions & 3 deletions build-tools/xaprepare/xaprepare/xaprepare.csproj
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../../Configuration.props" />

<PropertyGroup>
<TargetFramework>$(DotNetStableTargetFramework)</TargetFramework>
<OutputType>Exe</OutputType>
<LangVersion>8.0</LangVersion>
<LangVersion>$(LangVersion)</LangVersion>
<RootNamespace>Xamarin.Android.Prepare</RootNamespace>
<AssemblyName>xaprepare</AssemblyName>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>

<Import Project="../../../Configuration.props" />

<ItemGroup>
<Compile Include="$(IntermediateOutputPath)/BuildInfo.Generated.cs" />
<Compile Include="$(IntermediateOutputPath)/Properties.Defaults.cs" />
Expand Down
Loading