├── .gitignore
├── .travis.yml
├── Cactus.Fileserver.Aspnet
├── Cactus.Fileserver.Aspnet.csproj
├── Config
│ └── AppBuilderExtension.cs
├── Dto
│ └── ResponseDto.cs
├── HttpExtensions.cs
└── Middleware
│ ├── AddFileHandler.cs
│ ├── AddMultipartContentHandler.cs
│ ├── DeleteFileHandler.cs
│ ├── GetFileHandler.cs
│ └── RedirectToInternalStorageHandler.cs
├── Cactus.Fileserver.AzureStorage
├── AzureFileStorage.cs
└── Cactus.Fileserver.AzureStorage.csproj
├── Cactus.Fileserver.ImageResizer
├── Assembly.cs
├── Cactus.Fileserver.ImageResizer.csproj
├── ConfigurationExtensions.cs
├── DynamicResizeMiddleware.cs
├── HttpRequestExtension.cs
├── ImageResizerService.cs
└── Utils
│ └── ResizeInstructions.cs
├── Cactus.Fileserver.LocalStorage.Test
├── AllInTheSameFolderUriResolverTests.cs
├── Cactus.Fileserver.LocalStorage.Test.csproj
├── FolderUriResolverTest.cs
├── LocalFileStorageTests.cs
└── LocalMetaInfoStorageTests.cs
├── Cactus.Fileserver.LocalStorage
├── Assembly.cs
├── Cactus.Fileserver.LocalStorage.csproj
├── Config
│ ├── ConfigurationExtensions.cs
│ ├── LocalMetaStorageOptions.cs
│ └── LocalStorageOptions.cs
├── LocalFileStorage.cs
├── LocalMetaInfoStorage.cs
├── SingleFolderUriResolver.cs
├── SubfolderUriResolver.cs
└── UriResolver.cs
├── Cactus.Fileserver.S3Storage
├── Cactus.Fileserver.S3Storage.csproj
├── ConfigurationExtensions.cs
├── S3FileStorage.cs
├── S3FileStorageOptions.cs
└── UriResolver.cs
├── Cactus.Fileserver.Tests
├── Cactus.Fileserver.Tests.csproj
├── Integration
│ ├── CreateReadDeleteTest.cs
│ ├── FileserverTestHost.cs
│ └── ImageResizeTest.cs
├── Unit
│ ├── ImageResizerTest.cs
│ ├── RandomNameProviderTest.cs
│ ├── ResizeInstructionTest.cs
│ └── UriExtensionTest.cs
├── kartman.png
└── log4net.config
├── Cactus.Fileserver.sln
├── Cactus.Fileserver
├── Assembly.cs
├── Cactus.Fileserver.csproj
├── FileStorageService.cs
├── IFileStorageService.cs
├── Model
│ ├── IMetaInfo.cs
│ └── MetaInfo.cs
├── Storage
│ ├── IFileStorage.cs
│ ├── IMetaInfoStorage.cs
│ ├── IStoredNameProvider.cs
│ └── RandomNameProvider.cs
└── UriExtension.cs
├── Examples
└── Cactus.Fileserver.Simple
│ ├── Cactus.Fileserver.Simple.csproj
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── Startup.cs
│ ├── log4net.config
│ └── wwwroot
│ ├── .json
│ ├── 17GfJps2PCX06jSd.txt
│ ├── 3tenEHtSwZFfqXpx.txt
│ ├── 4sRpySbooGfEQXFW.jpg
│ ├── 4sRpySbooGfEQXFW.jpg.json
│ ├── 59kc8cD7lyITeniC.jpg
│ ├── 59kc8cD7lyITeniC.jpg.json
│ ├── 623U8TvsnTwOKYMU.jpg
│ ├── 623U8TvsnTwOKYMU.jpg.json
│ ├── NonEGsJqfdv7PZCd.jpg
│ ├── NonEGsJqfdv7PZCd.jpg.json
│ ├── RRmhkED4tndt0X1B.jpg
│ ├── RRmhkED4tndt0X1B.jpg.json
│ ├── Wqf0k8WL10Ht3JAc.jpg
│ ├── Wqf0k8WL10Ht3JAc.jpg.json
│ ├── X8Q6qShBQglPl_w8.jpg
│ ├── X8Q6qShBQglPl_w8.jpg.json
│ ├── _CgVvhMoija-Q-IV.txt
│ ├── bYqzr8vZ7V8xm53I.jpg
│ ├── bYqzr8vZ7V8xm53I.jpg.json
│ ├── dN7ucueKCfKmDb9D.jpg
│ ├── dN7ucueKCfKmDb9D.jpg.json
│ ├── eSyNpOSpJV55F8-v.txt
│ ├── fXINhOS0ku4DuR9U
│ ├── fXINhOS0ku4DuR9U.json
│ ├── i78fV6TsnMbaN6r8.jpg
│ ├── i78fV6TsnMbaN6r8.jpg.json
│ ├── juRt0OK0tWprDnBH.jpg
│ ├── juRt0OK0tWprDnBH.jpg.json
│ ├── njKrLZONPbU871tS.jpg
│ ├── njKrLZONPbU871tS.jpg.json
│ ├── nothing.txt
│ ├── zGQbOo2yH6L98skc.jpg
│ └── zGQbOo2yH6L98skc.jpg.json
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Download this file using PowerShell v3 under Windows with the following comand
2 | # Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # Build results
11 | [Dd]ebug/
12 | [Dd]ebugPublic/
13 | [Rr]elease/
14 | [Rr]eleases/
15 | x64/
16 | x86/
17 | build/
18 | bld/
19 | [Bb]in/
20 | [Oo]bj/
21 | [Bb]in/
22 | [Oo]bj/
23 |
24 | # NuGet Packages
25 | *.nupkg
26 | # The packages folder can be ignored because of Package Restore
27 | **/packages/*
28 | **/node_modules/*
29 | # except build/, which is used as an MSBuild target.
30 | !**/packages/build/
31 | # Uncomment if necessary however generally it will be regenerated when needed
32 | #!**/packages/repositories.config
33 |
34 | # MSTest test Results
35 | [Tt]est[Rr]esult*/
36 | [Bb]uild[Ll]og.*
37 |
38 | *_i.c
39 | *_p.c
40 | *.ilk
41 | *.meta
42 | *.obj
43 | *.pch
44 | *.pdb
45 | *.pgc
46 | *.pgd
47 | *.rsp
48 | *.sbr
49 | *.tlb
50 | *.tli
51 | *.tlh
52 | *.tmp
53 | *.tmp_proj
54 | *.log
55 | *.vspscc
56 | *.vssscc
57 | .builds
58 | *.pidb
59 | *.log
60 | *.scc
61 |
62 | # OS generated files #
63 | .DS_Store*
64 | Icon?
65 |
66 | # Visual C++ cache files
67 | ipch/
68 | *.aps
69 | *.ncb
70 | *.opensdf
71 | *.sdf
72 | *.cachefile
73 |
74 | # Visual Studio profiler
75 | *.psess
76 | *.vsp
77 | *.vspx
78 |
79 | # Guidance Automation Toolkit
80 | *.gpState
81 |
82 | # ReSharper is a .NET coding add-in
83 | _ReSharper*/
84 | *.[Rr]e[Ss]harper
85 |
86 | # TeamCity is a build add-in
87 | _TeamCity*
88 |
89 | # DotCover is a Code Coverage Tool
90 | *.dotCover
91 |
92 | # NCrunch
93 | *.ncrunch*
94 | .*crunch*.local.xml
95 |
96 | # Installshield output folder
97 | [Ee]xpress/
98 |
99 | # DocProject is a documentation generator add-in
100 | DocProject/buildhelp/
101 | DocProject/Help/*.HxT
102 | DocProject/Help/*.HxC
103 | DocProject/Help/*.hhc
104 | DocProject/Help/*.hhk
105 | DocProject/Help/*.hhp
106 | DocProject/Help/Html2
107 | DocProject/Help/html
108 |
109 | # Click-Once directory
110 | publish/
111 |
112 | # Publish Web Output
113 | *.Publish.xml
114 |
115 | # Windows Azure Build Output
116 | csx
117 | *.build.csdef
118 |
119 | # Windows Store app package directory
120 | AppPackages/
121 |
122 | # Others
123 | *.Cache
124 | ClientBin/
125 |
126 | ~$*
127 | *~
128 | *.dbmdl
129 | *.[Pp]ublish.xml
130 | *.pfx
131 | *.publishsettings
132 | modulesbin/
133 | tempbin/
134 |
135 | # EPiServer Site file (VPP)
136 | AppData/
137 |
138 | # RIA/Silverlight projects
139 | Generated_Code/
140 |
141 | # Backup & report files from converting an old project file to a newer
142 | # Visual Studio version. Backup files are not needed, because we have git ;-)
143 | _UpgradeReport_Files/
144 | Backup*/
145 | UpgradeLog*.XML
146 | UpgradeLog*.htm
147 |
148 | # vim
149 | *.txt~
150 | *.swp
151 | *.swo
152 |
153 | # svn
154 | .svn
155 |
156 | # Remainings from resolvings conflicts in Source Control
157 | *.orig
158 |
159 | # SQL Server files
160 | **/App_Data/*.mdf
161 | **/App_Data/*.ldf
162 | **/App_Data/*.sdf
163 |
164 |
165 | #LightSwitch generated files
166 | GeneratedArtifacts/
167 | _Pvt_Extensions/
168 | ModelManifest.xml
169 |
170 | # =========================
171 | # Windows detritus
172 | # =========================
173 |
174 | # Windows image file caches
175 | Thumbs.db
176 | ehthumbs.db
177 |
178 | # Folder config file
179 | Desktop.ini
180 |
181 | # Recycle Bin used on file shares
182 | $RECYCLE.BIN/
183 |
184 | # Mac desktop service store files
185 | .DS_Store
186 |
187 | # SASS Compiler cache
188 | .sass-cache
189 |
190 | # Visual Studio 2014 CTP
191 | **/*.sln.ide
192 |
193 | # Visual Studio temp something
194 | .vs/
195 |
196 | #####
197 | # End of core ignore list, below put you custom 'per project' settings (patterns or path)
198 | #####
199 | Sommelier.Api/Sommelier.Api/UploadFiles/
200 | Cactus.Fileserver.ImageResizer.Nuget/
201 | Cactus.Fileserver.Owin.Nuget/
202 | Examples/Cactus.Fileserver.Simple/wwwroot/
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: csharp
2 | mono: none
3 | dotnet: 3.1
4 | solution: Cactus.Fileserver.sln
5 | env:
6 | global:
7 | - DOTNET_CLI_TELEMETRY_OPTOUT: 1
8 | script:
9 | - dotnet restore
10 | - dotnet build
11 | - dotnet test Cactus.Fileserver.Tests/Cactus.Fileserver.Tests.csproj /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
12 | - dotnet test Cactus.Fileserver.LocalStorage.Test/Cactus.Fileserver.LocalStorage.Test.csproj /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
13 | after_script:
14 | - bash <(curl -s https://codecov.io/bash)
--------------------------------------------------------------------------------
/Cactus.Fileserver.Aspnet/Cactus.Fileserver.Aspnet.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | 4.0.4
6 | MIT
7 | https://github.com/CactusSoft/Cactus.Fileserver
8 | https://github.com/CactusSoft/Cactus.Fileserver
9 | GitHub
10 | true
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/Cactus.Fileserver.Aspnet/Config/AppBuilderExtension.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http;
3 | using Cactus.Fileserver.Aspnet.Middleware;
4 | using Microsoft.AspNetCore.Builder;
5 |
6 | namespace Cactus.Fileserver.Aspnet.Config
7 | {
8 | public static class AppBuilderExtension
9 | {
10 | public static IApplicationBuilder UseDelFile(this IApplicationBuilder app)
11 | {
12 | app.MapWhen(c => HttpMethod.Delete.Method.Equals(c.Request.Method, StringComparison.OrdinalIgnoreCase) &&
13 | c.Request.Path.HasValue,
14 | builder => builder.UseMiddleware());
15 | return app;
16 | }
17 |
18 | public static IApplicationBuilder UseDelFile(this IApplicationBuilder app)
19 | {
20 | app.MapWhen(c => HttpMethod.Delete.Method.Equals(c.Request.Method, StringComparison.OrdinalIgnoreCase) &&
21 | c.Request.Path.HasValue,
22 | builder => builder.UseMiddleware());
23 | return app;
24 | }
25 |
26 | public static IApplicationBuilder UseAddFile(this IApplicationBuilder app)
27 | {
28 | app.MapWhen(c => HttpMethod.Post.Method.Equals(c.Request.Method, StringComparison.OrdinalIgnoreCase),
29 | builder => builder
30 | .UseMiddleware()
31 | .UseMiddleware());
32 | return app;
33 | }
34 |
35 | public static IApplicationBuilder UseAddFile(this IApplicationBuilder app)
36 | {
37 | app.MapWhen(c => HttpMethod.Post.Method.Equals(c.Request.Method, StringComparison.OrdinalIgnoreCase),
38 | builder => builder.UseMiddleware());
39 | return app;
40 | }
41 |
42 | public static IApplicationBuilder UseGetFile(this IApplicationBuilder app, Action configuration)
43 | {
44 | app.MapWhen(c => HttpMethod.Get.Method.Equals(c.Request.Method, StringComparison.OrdinalIgnoreCase), configuration);
45 | return app;
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/Cactus.Fileserver.Aspnet/Dto/ResponseDto.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Cactus.Fileserver.Model;
5 |
6 | namespace Cactus.Fileserver.Aspnet.Dto
7 | {
8 | public class ResponseDto
9 | {
10 |
11 | public ResponseDto() { }
12 |
13 | public ResponseDto(IMetaInfo meta)
14 | {
15 | Uri = meta.Uri;
16 | Extra = meta.Extra?.ToDictionary(e => e.Key, e => e.Value);
17 | Owner = meta.Owner;
18 | Origin = meta.Origin;
19 | OriginalName = meta.OriginalName;
20 | MimeType = meta.MimeType;
21 | Icon = meta.Icon;
22 | }
23 | ///
24 | /// Not null if a error took place
25 | ///
26 | public string Error { get; set; }
27 | ///
28 | /// Full URL for getting the file. http://cdn.texas.srv.com/debug-folder/abcdf.png?x=y in our case
29 | ///
30 | public Uri Uri { get; set; }
31 |
32 | ///
33 | /// Origin URI.
34 | ///
35 | public Uri Origin { get; set; }
36 |
37 | ///
38 | /// The stored file MIME type regarding RFC6838
39 | /// In our case "image/png" because the original file was converted into PNG format during uploading
40 | ///
41 | public string MimeType { get; set; }
42 |
43 | ///
44 | /// The original uploaded file name, "pic.jpg" in our case
45 | ///
46 | public string OriginalName { get; set; }
47 |
48 | ///
49 | /// File owner. Any string that will help you to define the file owner, e-mail for example
50 | ///
51 | public string Owner { get; set; }
52 |
53 | ///
54 | /// Icon or thumbnail URI.
55 | ///
56 | public Uri Icon { get; set; }
57 |
58 | ///
59 | /// Any extra parameters. The point for lightweight extensions.
60 | ///
61 | public IDictionary Extra { get; set; }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Cactus.Fileserver.Aspnet/HttpExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net.Http.Headers;
3 | using Microsoft.AspNetCore.Http;
4 |
5 | namespace Cactus.Fileserver.Aspnet
6 | {
7 | internal static class HttpExtensions
8 | {
9 | public static Uri GetAbsoluteUri(this HttpRequest request)
10 | {
11 | return new Uri(string.Concat(
12 | request.Scheme,
13 | "://",
14 | request.Host.ToUriComponent(),
15 | request.PathBase.ToUriComponent(),
16 | request.Path.ToUriComponent(),
17 | request.QueryString.ToUriComponent()));
18 | }
19 |
20 | public static string GetFileName(this HttpContentHeaders headers)
21 | {
22 | return headers.ContentDisposition.FileName?.Trim('"');
23 | }
24 | }
25 | }
--------------------------------------------------------------------------------
/Cactus.Fileserver.Aspnet/Middleware/AddFileHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using System.Security.Principal;
3 | using System.Threading.Tasks;
4 | using Cactus.Fileserver.Aspnet.Dto;
5 | using Cactus.Fileserver.Model;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.Extensions.Logging;
8 | using Newtonsoft.Json;
9 |
10 | namespace Cactus.Fileserver.Aspnet.Middleware
11 | {
12 | ///
13 | /// Terminal handler for adding file. Put the request body into a file regardless of the content
14 | ///
15 | public class AddFileHandler
16 | {
17 | protected static readonly string JsonMimeType = "application/json";
18 | private readonly ILogger _log;
19 | protected readonly JsonSerializerSettings SerializerSettings;
20 |
21 | public AddFileHandler(RequestDelegate next, ILogger log)
22 | {
23 | _log = log;
24 | SerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
25 | }
26 |
27 | public async Task InvokeAsync(HttpContext ctx, IFileStorageService fileStorage)
28 | {
29 | _log.LogDebug("Store request content {content-type} into a file", ctx.Request.ContentType);
30 | var metaInfo = await ProcessUpload(ctx, fileStorage);
31 | await ResponseForUpload(ctx, metaInfo);
32 | _log.LogInformation("Served by {handler}", GetType().Name);
33 | }
34 |
35 | private async Task ResponseForUpload(HttpContext ctx, IMetaInfo metaInfo)
36 | {
37 | ctx.Response.StatusCode = (int)HttpStatusCode.Created;
38 | ctx.Response.Headers.Add("Location", metaInfo.Uri.ToString());
39 | ctx.Response.ContentType = JsonMimeType;
40 | await ctx.Response.WriteAsync(JsonConvert.SerializeObject(BuldOkResponseObject(metaInfo), SerializerSettings));
41 | }
42 |
43 | protected virtual async Task ProcessUpload(HttpContext ctx, IFileStorageService fileStorage)
44 | {
45 | var meta = BuildMetaInfo(ctx);
46 | await fileStorage.Create(ctx.Request.Body, meta);
47 | return meta;
48 | }
49 |
50 | protected virtual object BuldOkResponseObject(IMetaInfo meta)
51 | {
52 | return new ResponseDto(meta);
53 | }
54 |
55 | protected virtual IMetaInfo BuildMetaInfo(HttpContext ctx)
56 | {
57 | return new MetaInfo
58 | {
59 | Uri = ctx.Request.GetAbsoluteUri(),
60 | MimeType = ctx.Request.ContentType ?? "application/octet-stream",
61 | Owner = GetOwner(ctx.User.Identity)
62 | };
63 | }
64 |
65 | ///
66 | /// Returns a string that represent file owner based on authentication context.
67 | /// By default returns Identity.Name or nul if user is not authenticated.
68 | ///
69 | ///
70 | /// Owner as a string or null
71 | protected virtual string GetOwner(IIdentity identity)
72 | {
73 | return identity?.Name;
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/Cactus.Fileserver.Aspnet/Middleware/AddMultipartContentHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Net.Http;
6 | using System.Net.Http.Headers;
7 | using System.Security.Principal;
8 | using System.Threading.Tasks;
9 | using Cactus.Fileserver.Aspnet.Dto;
10 | using Cactus.Fileserver.Model;
11 | using Microsoft.AspNetCore.Http;
12 | using Microsoft.Extensions.Logging;
13 | using Newtonsoft.Json;
14 |
15 | namespace Cactus.Fileserver.Aspnet.Middleware
16 | {
17 | ///
18 | /// Adds files from multipart content if the request is multipart. Otherwise, continue pipeline
19 | ///
20 | public class AddFilesFromMultipartContentHandler
21 | {
22 | protected static readonly string JsonMimeType = "application/json";
23 | private readonly RequestDelegate _next;
24 | private readonly ILogger _log;
25 | protected readonly JsonSerializerSettings SerializerSettings;
26 |
27 | public AddFilesFromMultipartContentHandler(RequestDelegate next, ILogger log)
28 | {
29 | _next = next;
30 | _log = log;
31 | SerializerSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore};
32 | }
33 |
34 | public async Task InvokeAsync(HttpContext ctx, IFileStorageService fileStorage)
35 | {
36 | if (ctx.Request.ContentType?.StartsWith("multipart/", StringComparison.OrdinalIgnoreCase) ?? false)
37 | {
38 | var content = new StreamContent(ctx.Request.Body);
39 | content.Headers.ContentType = MediaTypeHeaderValue.Parse(ctx.Request.ContentType);
40 | var provider = await content.ReadAsMultipartAsync();
41 | var resList = new List