enforced file system

main
Paul Schneider 8 years ago
parent 2a523c7eae
commit edd91faa96
26 changed files with 143 additions and 76 deletions

@ -1,4 +1,5 @@
using System.Linq; using System.Linq;
using System.Text;
namespace Yavsc.Abstract.FileSystem namespace Yavsc.Abstract.FileSystem
{ {
@ -6,20 +7,35 @@ namespace Yavsc.Abstract.FileSystem
{ {
public static bool IsValidYavscPath(this string path) public static bool IsValidYavscPath(this string path)
{ {
if (path == null) return true; if (string.IsNullOrEmpty(path)) return true;
foreach (var name in path.Split('/')) foreach (var name in path.Split('/'))
{ {
if (!IsValidDirectoryName(name) || name.Equals("..") || name.Equals(".")) if (!IsValidDirectoryName(name) || name.Equals("..") || name.Equals("."))
return false; return false;
} }
if (path[path.Length]==FileSystemConstants.RemoteDirectorySeparator) return false; if (path[path.Length-1]==FileSystemConstants.RemoteDirectorySeparator) return false;
return true; return true;
} }
public static bool IsValidDirectoryName(this string name) public static bool IsValidDirectoryName(this string name)
{ {
return !name.Any(c => !FileSystemConstants.ValidFileNameChars.Contains(c)); return !name.Any(c => !FileSystemConstants.ValidFileNameChars.Contains(c));
} }
// Ensure this path is canonical,
// No "dirto/./this", neither "dirt/to/that/"
// no .. and each char must be listed as valid in constants
public static string FilterFileName(string fileName)
{
if (fileName==null) return null;
StringBuilder sb = new StringBuilder();
foreach (var c in fileName)
{
if (FileSystemConstants.ValidFileNameChars.Contains(c))
sb.Append(c);
else sb.Append('_');
}
return sb.ToString();
}
} }
public static class FileSystemConstants public static class FileSystemConstants

@ -0,0 +1,8 @@
namespace Yavsc.Abstract.FileSystem {
public interface IDirectoryShortInfo
{
string Name { get; set; }
bool IsEmpty { get; set; }
}
}

@ -0,0 +1,15 @@
namespace Yavsc.Abstract.FileSystem
{
public interface IFileRecievedInfo
{
string MimeType { get; set; }
string DestDir { get; set; }
string FileName { get; set; }
bool Overriden { get; set; }
bool QuotaOffensed { get; set; }
}
}

@ -7,15 +7,21 @@ namespace Yavsc.ViewModels.UserFiles
{ {
public class UserDirectoryInfo public class UserDirectoryInfo
{ {
public string UserName { get; private set; } public string UserName { get; set; }
public string SubPath { get; private set; } public string SubPath { get; set; }
public RemoteFileInfo [] Files { public RemoteFileInfo [] Files {
get; private set; get; set;
} }
public string [] SubDirectories {  public DirectoryShortInfo [] SubDirectories { 
get; private set; get; set;
} }
private DirectoryInfo dInfo; private DirectoryInfo dInfo;
// for deserialization
public UserDirectoryInfo()
{
}
public UserDirectoryInfo(string userReposPath, string username, string path) public UserDirectoryInfo(string userReposPath, string username, string path)
{ {
if (string.IsNullOrWhiteSpace(username)) if (string.IsNullOrWhiteSpace(username))
@ -35,7 +41,12 @@ namespace Yavsc.ViewModels.UserFiles
( entry => new RemoteFileInfo { Name = entry.Name, Size = entry.Length, ( entry => new RemoteFileInfo { Name = entry.Name, Size = entry.Length,
CreationTime = entry.CreationTime, LastModified = entry.LastWriteTime }).ToArray(); CreationTime = entry.CreationTime, LastModified = entry.LastWriteTime }).ToArray();
SubDirectories = dInfo.GetDirectories().Select SubDirectories = dInfo.GetDirectories().Select
( d=> d.Name ).ToArray(); ( d=> new DirectoryShortInfo { Name= d.Name, IsEmpty=false } ).ToArray();
} }
} }
public class DirectoryShortInfo: IDirectoryShortInfo {
public string Name { get; set; }
public bool IsEmpty { get; set; }
}
} }

@ -1,17 +1,23 @@
CONFIG=Release CONFIG=Release
VERSION=1.0.2 VERSION=1.0.5-rc4
PRJNAME=Yavsc.Abstract PRJNAME=Yavsc.Abstract
PKGFILENAME=$(PRJNAME).$(VERSION).nupkg PKGFILENAME=$(PRJNAME).$(VERSION).nupkg
DESTPATH=. DESTPATH=.
PACKAGE=$(DESTPATH)/$(PKGFILENAME) PACKAGE=$(DESTPATH)/$(PKGFILENAME)
BINARY=bin/$(CONFIG)/net45/Yavsc.Abstract.dll
NUGETSOURCE=$(HOME)/Nupkgs/
$(PACKAGE): $(PACKAGE): $(BINARY)
nuget pack $(PRJNAME).nuspec -Version $(VERSION) nuget pack $(PRJNAME).nuspec -Version $(VERSION) -Properties config=$(CONFIG)
publish: $(PACKAGE)
cp $(PACKAGE) ~/Nupkgs
clean: clean:
rm $(PACKAGE) rm $(PACKAGE)
$(BINARY): project.lock.json
dnu build --configuration $(CONFIG)
project.lock.json: project.json
dnu restore
deploy: $(PACKAGE)
cp $(PACKAGE) $(NUGETSOURCE)

@ -4,15 +4,15 @@ using System.Reflection;
// General Information about an assembly is controlled through the following // General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information // set of attributes. Change these attribute values to modify the information
// associated with an assembly. // associated with an assembly.
[assembly: AssemblyTitle("Yavsc.Client")] [assembly: AssemblyTitle("Yavsc.Abstract")]
[assembly: AssemblyDescription("")] [assembly: AssemblyDescription("Yavsc shared objects")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")] [assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Yavsc.Client")] [assembly: AssemblyProduct("Yavsc.Abstract")]
[assembly: AssemblyCopyright("Copyright © 2014")] [assembly: AssemblyCopyright("Copyright © 2014-2018")]
[assembly: AssemblyTrademark("")] [assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")] [assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")] [assembly: NeutralResourcesLanguage("fr")]
// Version information for an assembly consists of the following four values: // Version information for an assembly consists of the following four values:
// //
@ -24,5 +24,5 @@ using System.Reflection;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyVersion("1.0.5.*")]
[assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.5.2")]

@ -18,6 +18,6 @@
<tags>yavsc</tags> <tags>yavsc</tags>
</metadata> </metadata>
<files> <files>
<file src="bin/Release/net451/Yavsc.Abstract.dll" target="lib/portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10" /> <file src="bin/$config$/net451/Yavsc.Abstract.dll" target="lib/portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10" />
</files> </files>
</package> </package>

@ -1,5 +1,5 @@
{ {
"version": "1.0.2", "version": "1.0.5",
"description": "Yavsc Client Api", "description": "Yavsc Client Api",
"authors": [ "authors": [
"Paul Schneider" "Paul Schneider"

@ -10,8 +10,11 @@ using Yavsc.Models;
namespace Yavsc.ApiControllers namespace Yavsc.ApiControllers
{ {
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Yavsc.Abstract.FileSystem; using Yavsc.Abstract.FileSystem;
using Yavsc.Exceptions; using Yavsc.Exceptions;
using Yavsc.Models.FileSystem;
public class FSQuotaException : Exception { public class FSQuotaException : Exception {
} }
@ -21,12 +24,16 @@ namespace Yavsc.ApiControllers
{ {
ApplicationDbContext dbContext; ApplicationDbContext dbContext;
private IAuthorizationService AuthorizationService; private IAuthorizationService AuthorizationService;
private ILogger logger;
public FileSystemApiController(ApplicationDbContext context, public FileSystemApiController(ApplicationDbContext context,
IAuthorizationService authorizationService) IAuthorizationService authorizationService,
ILoggerFactory loggerFactory)
{ {
AuthorizationService = authorizationService; AuthorizationService = authorizationService;
dbContext = context; dbContext = context;
logger = loggerFactory.CreateLogger<FileSystemApiController>();
} }
[HttpGet()] [HttpGet()]
@ -46,11 +53,11 @@ namespace Yavsc.ApiControllers
} }
[HttpPost] [HttpPost]
public IEnumerable<IActionResult> Post(string subdir="", string names = null) public IActionResult Post(string subdir="", string names = null)
{ {
string root = null; string root = null;
string [] destinationFileNames = names?.Split('/'); string [] destinationFileNames = names?.Split('/');
List<FileRecievedInfo> received = new List<FileRecievedInfo>();
InvalidPathException pathex = null; InvalidPathException pathex = null;
try { try {
root = User.InitPostToFileSystem(subdir); root = User.InitPostToFileSystem(subdir);
@ -58,20 +65,26 @@ namespace Yavsc.ApiControllers
pathex = ex; pathex = ex;
} }
if (pathex!=null) if (pathex!=null)
yield return new BadRequestObjectResult(pathex); return new BadRequestObjectResult(pathex);
var uid = User.GetUserId();
var user = dbContext.Users.Single( var user = dbContext.Users.Single(
u => u.Id == User.GetUserId() u => u.Id == uid
); );
int i=0; int i=0;
logger.LogInformation($"Recieving {Request.Form.Files.Count} files.");
foreach (var f in Request.Form.Files) foreach (var f in Request.Form.Files)
{ {
var destFileName = destinationFileNames?.Length >i ? destinationFileNames[i] : null; var destFileName = destinationFileNames?.Length >i ? destinationFileNames[i] : null;
var item = user.ReceiveUserFile(root, f, destFileName); var item = user.ReceiveUserFile(root, f, destFileName);
dbContext.SaveChanges(User.GetUserId()); dbContext.SaveChanges(User.GetUserId());
yield return Ok(item); received.Add(item);
logger.LogInformation($"Recieved '{item.FileName}'.");
if (item.QuotaOffensed)
break;
i++;
}; };
return Ok(received);
} }
[HttpDelete] [HttpDelete]

@ -4,10 +4,8 @@ using System;
using System.Drawing; using System.Drawing;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Mime; using System.Net.Mime;
using System.Security.Claims; using System.Security.Claims;
using System.Text;
using System.Web; using System.Web;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Yavsc.Abstract.FileSystem; using Yavsc.Abstract.FileSystem;
@ -31,22 +29,7 @@ namespace Yavsc.Helpers
return di; return di;
} }
// Ensure this path is canonical,
// No "dirto/./this", neither "dirt/to/that/"
// no .. and each char must be listed as valid in constants
public static string FilterFileName(string fileName)
{
if (fileName==null) return null;
StringBuilder sb = new StringBuilder();
foreach (var c in fileName)
{
if (FileSystemConstants.ValidFileNameChars.Contains(c))
sb.Append(c);
else sb.Append('_');
}
return sb.ToString();
}
public static string InitPostToFileSystem( public static string InitPostToFileSystem(
this ClaimsPrincipal user, this ClaimsPrincipal user,
string subpath) string subpath)
@ -81,10 +64,14 @@ namespace Yavsc.Helpers
var item = new FileRecievedInfo(); var item = new FileRecievedInfo();
// form-data; name="file"; filename="capt0008.jpg" // form-data; name="file"; filename="capt0008.jpg"
ContentDisposition contentDisposition = new ContentDisposition(f.ContentDisposition); ContentDisposition contentDisposition = new ContentDisposition(f.ContentDisposition);
item.FileName = FilterFileName (destFileName ?? contentDisposition.FileName); item.FileName = Yavsc.Abstract.FileSystem.FileSystemHelpers.FilterFileName (destFileName ?? contentDisposition.FileName);
item.MimeType = contentDisposition.DispositionType; item.MimeType = contentDisposition.DispositionType;
var fi = new FileInfo(Path.Combine(root, item.FileName)); var fi = new FileInfo(Path.Combine(root, item.FileName));
if (fi.Exists) item.Overriden = true; if (fi.Exists)
{
item.Overriden = true;
usage -= fi.Length;
}
using (var dest = fi.OpenWrite()) using (var dest = fi.OpenWrite())
{ {
using (var org = f.OpenReadStream()) using (var org = f.OpenReadStream())
@ -92,7 +79,7 @@ namespace Yavsc.Helpers
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
long len = org.Length; long len = org.Length;
if (len > (user.DiskQuota - usage)) { if (len > (user.DiskQuota - usage)) {
item.QuotaOffensed = true;
return item; return item;
} }
usage += len; usage += len;

@ -21,13 +21,23 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
using Yavsc.Abstract.FileSystem;
namespace Yavsc.Models.FileSystem namespace Yavsc.Models.FileSystem
{ {
public class FileRecievedInfo public class FileRecievedInfo : IFileRecievedInfo
{
public FileRecievedInfo()
{ {
public string MimeType { get; set; } QuotaOffensed = Overriden = false;
public string DestDir { get; set; } MimeType = DestDir = FileName = null;
public string FileName { get; set; }
public bool Overriden { get; set; }
} }
public string MimeType { get; set; }
public string DestDir { get; set; }
public string FileName { get; set; }
public bool Overriden { get; set; }
public bool QuotaOffensed { get; set; }
}
} }

@ -28,7 +28,8 @@ namespace Yavsc
{ {
FileProvider = new PhysicalFileProvider(UserFilesDirName), FileProvider = new PhysicalFileProvider(UserFilesDirName),
RequestPath = new PathString(Constants.UserFilesPath), RequestPath = new PathString(Constants.UserFilesPath),
EnableDirectoryBrowsing = env.IsDevelopment() EnableDirectoryBrowsing = env.IsDevelopment(),
}; };
UserFilesOptions.EnableDefaultFiles=true; UserFilesOptions.EnableDefaultFiles=true;

@ -67,21 +67,17 @@
<div class="row @trclass"> <div class="row @trclass">
<div class="col-xs-10"> <div class="col-xs-10">
<a asp-action="Title" asp-route-id="@title" > <a asp-action="Title" asp-route-id="@title" >
<markdown>@first.Title</markdown> <h3 markdown="@first.Title"></h3>
@if (first.Photo==null) { } @if (first.Photo==null) { }
else {<img src="@first.Photo" class="blogphoto" alt="@first.Title">} else {<img src="@first.Photo" class="blogphoto" alt="@first.Title">}
</a> </a>
<markdown>@((first.Content?.Length > 120) ? first.Content.Substring(0, 120) + " ..." : first.Content)</markdown> <div class="smalltext" markdown="@((first.Content?.Length > 256) ? first.Content.Substring(0, 256) + "..." : first.Content)"></div>
<span style="font-size:x-small;">(@first.Author.UserName </span>, <div class="smalltext"> @first.Author.UserName ,
<span style="font-size:xx-small;"> (posté le @first.DateCreated.ToString("dddd d MMM yyyy à H:mm")
posté le @first.DateCreated.ToString("dddd d MMM yyyy à H:mm")
@if ((first.DateModified - first.DateCreated).Minutes > 0){  @if ((first.DateModified - first.DateCreated).Minutes > 0){ 
@:- Modifié le @first.DateModified.ToString("dddd d MMM yyyy à H:mm",System.Globalization.CultureInfo.CurrentUICulture) @:- Modifié le @first.DateModified.ToString("dddd d MMM yyyy à H:mm",System.Globalization.CultureInfo.CurrentUICulture)
}) })</div>
</span>
</div> </div>
<div class="col-xs-2"> <div class="col-xs-2">
<ul class="actiongroup"> <ul class="actiongroup">

@ -30,7 +30,7 @@
<img src="@item.Photo" class="blogphoto"></a> <img src="@item.Photo" class="blogphoto"></a>
</td> </td>
<td> <td>
<markdown>@((item.Content?.Length > 120) ? item.Content.Substring(0, 122) + " ..." : item.Content)</markdown> <markdown>@((item.Content?.Length > 256) ? item.Content.Substring(0, 256) + " ..." : item.Content)</markdown>
<span style="font-size:x-small;">(@item.Author.UserName </span>, <span style="font-size:x-small;">(@item.Author.UserName </span>,
<span style="font-size:xx-small;"> <span style="font-size:xx-small;">
posté le @item.DateCreated.ToString("dddd d MMM yyyy à H:mm") posté le @item.DateCreated.ToString("dddd d MMM yyyy à H:mm")

@ -142,7 +142,7 @@
@if (Model.DiskQuota>0) @if (Model.DiskQuota>0)
{ {
<text> <text>
@((Model.DiskUsage/Model.DiskQuota).ToString("%#0")) : @(((double)Model.DiskUsage/Model.DiskQuota).ToString("%#0")) :
</text> </text>
} }
<text> <text>

@ -3,8 +3,8 @@
<div class="dirinfo"> <div class="dirinfo">
<strong>@Model.UserName / @Model.SubPath</strong> <strong>@Model.UserName / @Model.SubPath</strong>
<div class="subdirs"> <div class="subdirs">
@foreach (string subdir in Model.SubDirectories) { @foreach (var subdir in Model.SubDirectories) {
<a href="@subdir" class="subdir">@subdir</a> <a href="@subdir" class="subdir">@subdir.Name</a>
} }
</div> </div>
<style> <style>

@ -114,7 +114,7 @@
"System.Json": "4.0.20126.16343", "System.Json": "4.0.20126.16343",
"Yavsc.Abstract": { "Yavsc.Abstract": {
"type": "build", "type": "build",
"version": "1.0.2" "version": "1.0.5"
}, },
"Extensions.AspNet.Authentication.Instagram": "1.0.0-t150809211713", "Extensions.AspNet.Authentication.Instagram": "1.0.0-t150809211713",
"Microsoft.AspNet.Http.Extensions": "1.0.0-rc1-final", "Microsoft.AspNet.Http.Extensions": "1.0.0-rc1-final",
@ -166,4 +166,4 @@
"postpublish": "echo after publish" "postpublish": "echo after publish"
}, },
"embed": "Views/**/*.cshtml" "embed": "Views/**/*.cshtml"
} }

@ -2823,7 +2823,7 @@
"lib/WebGrease.dll": {} "lib/WebGrease.dll": {}
} }
}, },
"Yavsc.Abstract/1.0.2": { "Yavsc.Abstract/1.0.5": {
"type": "project", "type": "project",
"framework": ".NETFramework,Version=v4.5.1", "framework": ".NETFramework,Version=v4.5.1",
"dependencies": { "dependencies": {
@ -5656,7 +5656,7 @@
"lib/WebGrease.dll": {} "lib/WebGrease.dll": {}
} }
}, },
"Yavsc.Abstract/1.0.2": { "Yavsc.Abstract/1.0.5": {
"type": "project", "type": "project",
"framework": ".NETFramework,Version=v4.5.1", "framework": ".NETFramework,Version=v4.5.1",
"dependencies": { "dependencies": {
@ -8489,7 +8489,7 @@
"lib/WebGrease.dll": {} "lib/WebGrease.dll": {}
} }
}, },
"Yavsc.Abstract/1.0.2": { "Yavsc.Abstract/1.0.5": {
"type": "project", "type": "project",
"framework": ".NETFramework,Version=v4.5.1", "framework": ".NETFramework,Version=v4.5.1",
"dependencies": { "dependencies": {
@ -8503,7 +8503,7 @@
} }
}, },
"libraries": { "libraries": {
"Yavsc.Abstract/1.0.2": { "Yavsc.Abstract/1.0.5": {
"type": "project", "type": "project",
"path": "../Yavsc.Abstract/project.json" "path": "../Yavsc.Abstract/project.json"
}, },
@ -11437,7 +11437,7 @@
"Microsoft.AspNet.Mvc.Formatters.Json >= 6.0.0-rc1-final", "Microsoft.AspNet.Mvc.Formatters.Json >= 6.0.0-rc1-final",
"Microsoft.AspNet.OWin >= 1.0.0-rc1-final", "Microsoft.AspNet.OWin >= 1.0.0-rc1-final",
"System.Json >= 4.0.20126.16343", "System.Json >= 4.0.20126.16343",
"Yavsc.Abstract >= 1.0.2", "Yavsc.Abstract >= 1.0.5",
"Extensions.AspNet.Authentication.Instagram >= 1.0.0-t150809211713", "Extensions.AspNet.Authentication.Instagram >= 1.0.0-t150809211713",
"Microsoft.AspNet.Http.Extensions >= 1.0.0-rc1-final", "Microsoft.AspNet.Http.Extensions >= 1.0.0-rc1-final",
"Microsoft.DiaSymReader.Native >= 1.5.0", "Microsoft.DiaSymReader.Native >= 1.5.0",

@ -390,6 +390,10 @@ p.small {
font-size: 16px; font-size: 16px;
} }
.smalltext {
font-size: 10px;
}
h1, h1,
h2, h2,
h3, h3,

Loading…