Skip to content
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
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: nuget
directory: /
schedule:
interval: weekly
groups:
test-dependencies:
patterns: ["*"]

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI

on:
push:
branches: [master]
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5

- uses: actions/setup-dotnet@v5
with:
dotnet-version: |
8.0.x
10.0.x

- name: Restore
run: dotnet restore EasyFileTransfer.sln

- name: Build
run: dotnet build EasyFileTransfer.sln --configuration Release --no-restore

- name: Test
run: dotnet test EasyFileTransfer.Tests/EasyFileTransfer.Tests.csproj --configuration Release --no-build

- name: Check for vulnerable packages
shell: bash
run: |
dotnet list EasyFileTransfer.sln package --vulnerable --include-transitive 2>&1 | tee vulnerable.txt
! grep -q -i "has the following vulnerable packages" vulnerable.txt

- name: Pack
if: matrix.os == 'ubuntu-latest'
run: dotnet pack EasyFileTransfer/EasyFileTransfer.csproj --configuration Release --no-build --output artifacts

- uses: actions/upload-artifact@v4
if: matrix.os == 'ubuntu-latest'
with:
name: nuget
path: artifacts/*
26 changes: 26 additions & 0 deletions EasyFileTransfer.Tests/EasyFileTransfer.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\EasyFileTransfer\EasyFileTransfer.csproj" />
</ItemGroup>

</Project>
51 changes: 51 additions & 0 deletions EasyFileTransfer.Tests/FileNameValidatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using EasyFileTransfer.Internal;

namespace EasyFileTransfer.Tests;

public class FileNameValidatorTests
{
[Theory]
[InlineData("report.pdf")]
[InlineData("archive.tar.gz")]
[InlineData("no-extension")]
[InlineData(".hidden")]
[InlineData("name with spaces.txt")]
[InlineData("\u0641\u0627\u06CC\u0644.txt")]
[InlineData("console.log")]
[InlineData("CONNECT.txt")]
public void Accepts_ordinary_names(string name) => Assert.True(FileNameValidator.IsValid(name));

[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(".")]
[InlineData("..")]
[InlineData("../evil.txt")]
[InlineData("..\\evil.txt")]
[InlineData("sub/file.txt")]
[InlineData("sub\\file.txt")]
[InlineData("/etc/passwd")]
[InlineData("C:\\Windows\\win.ini")]
[InlineData("C:evil.txt")]
[InlineData("file.txt:stream")]
[InlineData("\\\\server\\share\\x")]
[InlineData("trailing-dot.")]
[InlineData("trailing-space ")]
[InlineData("nul\0byte")]
[InlineData("new\nline")]
[InlineData("wild*card")]
[InlineData("CON")]
[InlineData("con.txt")]
[InlineData("NUL.tar.gz")]
[InlineData("COM1")]
[InlineData("lpt9.log")]
[InlineData("COM\u00B9")]
public void Rejects_dangerous_names(string name) => Assert.False(FileNameValidator.IsValid(name));

[Fact]
public void Rejects_names_longer_than_255_characters()
{
Assert.True(FileNameValidator.IsValid(new string('a', 255)));
Assert.False(FileNameValidator.IsValid(new string('a', 256)));
}
}
67 changes: 67 additions & 0 deletions EasyFileTransfer.Tests/RawClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Net.Sockets;
using System.Security.Cryptography;
using EasyFileTransfer.Internal;

namespace EasyFileTransfer.Tests;

/// <summary>Speaks the wire protocol by hand so tests can send what a real client never would.</summary>
internal sealed class RawClient : IDisposable
{
private readonly TcpClient _tcp;

public RawClient(int port)
{
_tcp = new TcpClient();
_tcp.Connect("127.0.0.1", port);
Stream = _tcp.GetStream();
Stream.ReadTimeout = 15000;
}

public NetworkStream Stream { get; }

public byte[] ReadHello()
{
var hello = new byte[Protocol.HelloLength];
Stream.ReadExactly(hello);
return hello;
}

public static byte[] Nonce(byte[] hello) => hello.AsSpan(Protocol.MagicLength + 3, Protocol.NonceLength).ToArray();

public void SendRequest(byte[] nameBytes, long length, byte[]? proof = null, byte version = Protocol.Version)
{
var request = new byte[Protocol.RequestHeaderLength + nameBytes.Length + 8];
Protocol.WriteMagic(request, 0);
request[Protocol.MagicLength] = version;
(proof ?? new byte[Protocol.ProofLength]).CopyTo(request, Protocol.MagicLength + 1);
Protocol.WriteUInt16(request, Protocol.RequestHeaderLength - 2, nameBytes.Length);
nameBytes.CopyTo(request, Protocol.RequestHeaderLength);
Protocol.WriteInt64(request, Protocol.RequestHeaderLength + nameBytes.Length, length);
Stream.Write(request);
}

public void SendRequest(string name, long length, byte[]? proof = null) =>
SendRequest(Protocol.StrictUtf8.GetBytes(name), length, proof);

public void SendContent(byte[] content, byte[]? hash = null)
{
Stream.Write(content);
Stream.Write(hash ?? SHA256.HashData(content));
}

/// <summary>Reads one status byte, or returns null if the server closed the connection.</summary>
public EftStatus? ReadStatus()
{
try
{
int b = Stream.ReadByte();
return b < 0 ? null : (EftStatus)b;
}
catch (IOException)
{
return null;
}
}

public void Dispose() => _tcp.Dispose();
}
Loading
Loading