├── .gitignore
├── AWSCLI.md
├── AwsCliTest.bat
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── DONATIONS.md
├── Docker
├── compose-down.bat
├── compose-down.sh
├── compose-up.bat
├── compose-up.sh
├── compose.yaml
├── less3.db
├── run.bat
├── run.sh
└── system.json
├── LICENSE.md
├── Less3.postman_collection
├── README.md
├── assets
├── CompatibilityTests.xlsx
├── diagram.png
├── diagram.pptx
├── favicon.ico
├── favicon.png
├── heart.ico
├── heart.png
├── heart.webp
├── icon.ico
└── logo.png
├── cyberduck
├── Less3-pathstyle-http.cyberduckprofile
└── Less3-virtualhosted.cyberduckprofile
└── src
├── Less3.sln
└── Less3
├── Api
├── Admin
│ ├── AdminApiHandler.cs
│ ├── DeleteHandler.cs
│ ├── GetHandler.cs
│ └── PostHandler.cs
└── S3
│ ├── ApiHandler.cs
│ ├── ApiHelper.cs
│ ├── BucketHandler.cs
│ ├── ObjectHandler.cs
│ └── ServiceHandler.cs
├── Assets
├── favicon.ico
└── favicon.png
├── Classes
├── AuthManager.cs
├── AuthenticationResult.cs
├── AuthorizationResult.cs
├── Bucket.cs
├── BucketAcl.cs
├── BucketClient.cs
├── BucketManager.cs
├── BucketStatistics.cs
├── BucketTag.cs
├── ConfigManager.cs
├── ConsoleManager.cs
├── Constants.cs
├── Credential.cs
├── Obj.cs
├── ObjectAcl.cs
├── ObjectTag.cs
├── RequestMetadata.cs
├── RetentionType.cs
├── Settings.cs
├── Setup.cs
└── User.cs
├── Common.cs
├── Dockerbuild.bat
├── Dockerfile
├── Dockerrun.bat
├── Dockerrun.sh
├── LICENSE.md
├── Less3.csproj
├── Less3.xml
├── Program.cs
├── Storage
├── DiskStorageDriver.cs
├── ObjectStream.cs
├── StorageDriver.cs
└── StorageDriverType.cs
├── clean.bat
├── clean.sh
└── heart.ico
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Visual Studio cache/options/bin directory
14 | .vs/
15 | bin/
16 | obj/
17 |
18 | # MSTest test Results
19 | [Tt]est[Rr]esult*/
20 | [Bb]uild[Ll]og.*
21 |
22 | # NUNIT
23 | *.VisualState.xml
24 | TestResult.xml
25 |
26 | # Build Results of an ATL Project
27 | [Dd]ebugPS/
28 | [Rr]eleasePS/
29 | dlldata.c
30 |
31 | # DNX
32 | project.lock.json
33 | project.fragment.lock.json
34 | artifacts/
35 |
36 | *_i.c
37 | *_p.c
38 | *_i.h
39 | *.ilk
40 | *.meta
41 | *.obj
42 | *.pch
43 | *.pdb
44 | *.pgc
45 | *.pgd
46 | *.rsp
47 | *.sbr
48 | *.tlb
49 | *.tli
50 | *.tlh
51 | *.tmp
52 | *.tmp_proj
53 | *.log
54 | *.vspscc
55 | *.vssscc
56 | .builds
57 | *.pidb
58 | *.svclog
59 | *.scc
60 |
61 | # Chutzpah Test files
62 | _Chutzpah*
63 |
64 | # Visual C++ cache files
65 | ipch/
66 | *.aps
67 | *.ncb
68 | *.opendb
69 | *.opensdf
70 | *.sdf
71 | *.cachefile
72 | *.VC.db
73 | *.VC.VC.opendb
74 |
75 | # Visual Studio profiler
76 | *.psess
77 | *.vsp
78 | *.vspx
79 | *.sap
80 |
81 | # TFS 2012 Local Workspace
82 | $tf/
83 |
84 | # Guidance Automation Toolkit
85 | *.gpState
86 |
87 | # ReSharper is a .NET coding add-in
88 | _ReSharper*/
89 | *.[Rr]e[Ss]harper
90 | *.DotSettings.user
91 |
92 | # JustCode is a .NET coding add-in
93 | .JustCode
94 |
95 | # TeamCity is a build add-in
96 | _TeamCity*
97 |
98 | # DotCover is a Code Coverage Tool
99 | *.dotCover
100 |
101 | # Visual Studio code coverage results
102 | *.coverage
103 | *.coveragexml
104 |
105 | # NCrunch
106 | _NCrunch_*
107 | .*crunch*.local.xml
108 | nCrunchTemp_*
109 |
110 | # MightyMoose
111 | *.mm.*
112 | AutoTest.Net/
113 |
114 | # Web workbench (sass)
115 | .sass-cache/
116 |
117 | # Installshield output folder
118 | [Ee]xpress/
119 |
120 | # DocProject is a documentation generator add-in
121 | DocProject/buildhelp/
122 | DocProject/Help/*.HxT
123 | DocProject/Help/*.HxC
124 | DocProject/Help/*.hhc
125 | DocProject/Help/*.hhk
126 | DocProject/Help/*.hhp
127 | DocProject/Help/Html2
128 | DocProject/Help/html
129 |
130 | # Click-Once directory
131 | publish/
132 |
133 | # Publish Web Output
134 | *.[Pp]ublish.xml
135 | *.azurePubxml
136 | # TODO: Comment the next line if you want to checkin your web deploy settings
137 | # but database connection strings (with potential passwords) will be unencrypted
138 | *.pubxml
139 | *.publishproj
140 |
141 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
142 | # checkin your Azure Web App publish settings, but sensitive information contained
143 | # in these scripts will be unencrypted
144 | PublishScripts/
145 |
146 | # NuGet Packages
147 | *.nupkg
148 | # The packages folder can be ignored because of Package Restore
149 | **/packages/*
150 | # except build/, which is used as an MSBuild target.
151 | !**/packages/build/
152 | # Uncomment if necessary however generally it will be regenerated when needed
153 | #!**/packages/repositories.config
154 | # NuGet v3's project.json files produces more ignoreable files
155 | *.nuget.props
156 | *.nuget.targets
157 |
158 | # Microsoft Azure Build Output
159 | csx/
160 | *.build.csdef
161 |
162 | # Microsoft Azure Emulator
163 | ecf/
164 | rcf/
165 |
166 | # Windows Store app package directories and files
167 | AppPackages/
168 | BundleArtifacts/
169 | Package.StoreAssociation.xml
170 | _pkginfo.txt
171 |
172 | # Visual Studio cache files
173 | # files ending in .cache can be ignored
174 | *.[Cc]ache
175 | # but keep track of directories ending in .cache
176 | !*.[Cc]ache/
177 |
178 | # Others
179 | ClientBin/
180 | ~$*
181 | *~
182 | *.dbmdl
183 | *.dbproj.schemaview
184 | *.jfm
185 | *.pfx
186 | *.publishsettings
187 | node_modules/
188 | orleans.codegen.cs
189 |
190 | # Since there are multiple workflows, uncomment next line to ignore bower_components
191 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
192 | #bower_components/
193 |
194 | # RIA/Silverlight projects
195 | Generated_Code/
196 |
197 | # Backup & report files from converting an old project file
198 | # to a newer Visual Studio version. Backup files are not needed,
199 | # because we have git ;-)
200 | _UpgradeReport_Files/
201 | Backup*/
202 | UpgradeLog*.XML
203 | UpgradeLog*.htm
204 |
205 | # SQL Server files
206 | *.mdf
207 | *.ldf
208 |
209 | # Business Intelligence projects
210 | *.rdl.data
211 | *.bim.layout
212 | *.bim_*.settings
213 |
214 | # Microsoft Fakes
215 | FakesAssemblies/
216 |
217 | # GhostDoc plugin setting file
218 | *.GhostDoc.xml
219 |
220 | # Node.js Tools for Visual Studio
221 | .ntvs_analysis.dat
222 |
223 | # Visual Studio 6 build log
224 | *.plg
225 |
226 | # Visual Studio 6 workspace options file
227 | *.opt
228 |
229 | # Visual Studio LightSwitch build output
230 | **/*.HTMLClient/GeneratedArtifacts
231 | **/*.DesktopClient/GeneratedArtifacts
232 | **/*.DesktopClient/ModelManifest.xml
233 | **/*.Server/GeneratedArtifacts
234 | **/*.Server/ModelManifest.xml
235 | _Pvt_Extensions
236 |
237 | # Paket dependency manager
238 | .paket/paket.exe
239 | paket-files/
240 |
241 | # FAKE - F# Make
242 | .fake/
243 |
244 | # JetBrains Rider
245 | .idea/
246 | *.sln.iml
247 |
248 | # CodeRush
249 | .cr/
250 |
251 | # Python Tools for Visual Studio (PTVS)
252 | __pycache__/
253 | *.pyc
254 |
255 | # Cake - Uncomment if you are using it
256 | # tools/
257 |
258 |
--------------------------------------------------------------------------------
/AWSCLI.md:
--------------------------------------------------------------------------------
1 | # Testing with AWS CLI
2 |
3 | Use the following commands to test a fresh installation of Less3 with the AWS CLI.
4 |
5 | Important: if you encounter any issues, re-submit the AWS CLI command using the ```--debug``` option, and please provide that output when filing an issue.
6 |
7 | ## Install Less3
8 | ```
9 | > less3
10 |
11 |
12 | _ ____
13 | | |___ _____|__ /
14 | | / -_|_-<_-<|_ \
15 | |_\___/__/__/___/
16 |
17 |
18 |
19 |
20 | <3 :: Less3 :: S3-Compatible Object Storage
21 |
22 | Thank you for using Less3! We're putting together a basic system configuration
23 | so you can be up and running quickly. You'll want to modify the system.json
24 | file after to ensure a more secure operating environment.
25 |
26 |
27 | Less3 requires access to a database and supports Sqlite, Microsoft SQL Server,
28 | MySQL, and PostgreSQL. Please provide access details for your database. The
29 | user account supplied must have the ability to CREATE and DROP tables along
30 | with issue queries containing SELECT, INSERT, UPDATE, and DELETE. Setup will
31 | attempt to create tables on your behalf if they dont exist.
32 |
33 | Database type [sqlite|sqlserver|mysql|postgresql]: [sqlite]
34 | Filename: [./less3.db]
35 |
36 | IMPORTANT: Using Sqlite in production is not recommended if deploying within a
37 | containerized environment and the database file is stored within the container.
38 | Store the database file in external storage to ensure persistence.
39 |
40 |
41 | All finished!
42 |
43 | If you ever want to return to this setup wizard, just re-run the application
44 | from the terminal with the 'setup' argument.
45 |
46 | We created a bucket containing a few sample files for you so that you can see
47 | your node in action. Access these files in the 'default' bucket using the
48 | AWS SDK or your favorite S3 browser tool.
49 |
50 | http://localhost:8000/default/hello.html
51 | http://localhost:8000/default/hello.txt
52 | http://localhost:8000/default/hello.json
53 |
54 | Access key : default
55 | Secret key : default
56 | Bucket name : default (public read enabled!)
57 | S3 endpoint : http://localhost:8000
58 |
59 | IMPORTANT: be sure to supply a hostname in the system.json Server.DnsHostname
60 | field if you wish to allow access from other machines. Your node is currently
61 | only accessible via localhost. Do not use an IP address for this value.
62 |
63 |
64 |
65 |
66 | ,d88b.d88b, _ ____
67 | 88888888888 | |___ _____|__ /
68 | `Y8888888Y' | / -_|_-<_-<|_ \
69 | `Y888Y' |_\___/__/__/___/
70 | `Y'
71 |
72 |
73 |
74 | Less3 | S3-Compatible Object Storage | v2.1.0.0
75 |
76 | WARNING: Less3 started on 'localhost'
77 | Less3 can only service requests from the local machine. If you wish to serve
78 | external requests, edit the system.json file and specify a DNS-resolvable
79 | hostname in the Server.DnsHostname field.
80 |
81 | | Initializing logging
82 | | Initializing database
83 | | Initializing configuration manager
84 | | Initializing bucket manager
85 | | Initializing authentication manager
86 | | Initializing API handler
87 | | Initializing admin API handler
88 | | Initializing console manager
89 | | Initializing S3 server interface
90 | | http://localhost:8000
91 | | Initializing S3 server APIs
92 | | No base domain specified
93 | | Requests must use path-style hosted URLs, i.e. [hostname]/[bucket]/[key]
94 |
95 | Command (? for help) >
96 | ```
97 |
98 | ## Set Access Material
99 | ```
100 | $ aws configure
101 | AWS Access Key ID [****************ault]: default
102 | AWS Secret Access Key [****************ault]: default
103 | Default region name [us-west-1]: us-west-1
104 | Default output format [None]:
105 | ```
106 |
107 | ## List Buckets
108 | ```
109 | $ aws --endpoint-url http://localhost:8000 s3 ls s3://
110 | 2022-09-14 21:43:27 default
111 | ```
112 |
113 | ## Check Bucket Existence
114 | ```
115 | $ aws s3api head-bucket --endpoint http://localhost:8000 --bucket default
116 | (no output)
117 | ```
118 |
119 | ## List Default Bucket
120 | ```
121 | $ aws --endpoint-url http://localhost:8000 s3 ls s3://default
122 | 2022-09-14 21:43:27 1193 hello.html
123 | 2022-09-14 21:43:27 217 hello.txt
124 | 2022-09-14 21:43:27 152 hello.json
125 | ```
126 |
127 | ## Download an Object
128 | ```
129 | $ aws --endpoint-url http://localhost:8000 s3 cp s3://default/hello.json ./hello.json
130 | download: s3://default/hello.json to .\hello.json
131 | ```
132 |
133 | ## Upload an Object
134 |
135 | Versioning is disabled by default on buckets. Re-uploading the object that was just downloaded would fail. Rename the object first.
136 |
137 | ```
138 | $ aws --endpoint-url http://localhost:8000 s3 cp ./hello.foo s3://default/hello.foo
139 | upload: .\hello.foo to s3://default/hello.foo
140 | ```
141 |
142 | ## Check Object Existence
143 | ```
144 | $ aws s3api head-object --endpoint http://localhost:8000 --bucket default --key hello.txt
145 | {
146 | "LastModified": "2022-09-14T21:43:27",
147 | "ContentLength": 217,
148 | "ETag": "626A3F7A01F364E917A5088E4856CADD",
149 | "ContentType": "application/octet-stream",
150 | "Metadata": {},
151 | "StorageClass": "STANDARD"
152 | }
153 | ```
154 |
155 | ## Delete an Object
156 | ```
157 | $ aws --endpoint-url http://localhost:8000 s3 rm s3://default/hello.foo
158 | delete: s3://default/hello.foo
159 | ```
160 |
161 | ## Create a Bucket
162 | ```
163 | $ aws --endpoint-url http://localhost:8000 s3 mb s3://bucket2
164 | make_bucket: bucket2
165 | ```
166 |
167 | ## Remove a Bucket
168 | ```
169 | $ aws --endpoint-url http://localhost:8000 s3 rb s3://bucket2
170 | remove_bucket: bucket2
171 | ```
172 |
--------------------------------------------------------------------------------
/AwsCliTest.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | aws configure
3 |
4 | echo Listing buckets...
5 | aws --endpoint-url http://localhost:8000 s3 ls s3://
6 |
7 | echo Checking if bucket default exists...
8 | aws s3api head-bucket --endpoint http://localhost:8000 --bucket default
9 |
10 | echo Listing bucket default...
11 | aws --endpoint-url http://localhost:8000 s3 ls s3://default
12 |
13 | echo Downloading hello.json...
14 | aws --endpoint-url http://localhost:8000 s3 cp s3://default/hello.json ./hello.json
15 |
16 | echo Copying default.json to hello.foo...
17 | copy /Y hello.json hello.foo
18 |
19 | echo Uploading hello.foo...
20 | aws --endpoint-url http://localhost:8000 s3 cp ./hello.foo s3://default/hello.foo
21 |
22 | echo Checking if object hello.foo exists...
23 | aws s3api head-object --endpoint http://localhost:8000 --bucket default --key hello.foo
24 |
25 | echo Deleting hello.foo...
26 | aws --endpoint-url http://localhost:8000 s3 rm s3://default/hello.foo
27 |
28 | echo Creating bucket bucket2...
29 | aws --endpoint-url http://localhost:8000 s3 mb s3://bucket2
30 |
31 | echo Deleting bucket bucket2...
32 | aws --endpoint-url http://localhost:8000 s3 rb s3://bucket2
33 |
34 | echo Cleaning up temporary files...
35 | del /q hello.foo
36 | del /q hello.json
37 | @echo on
38 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## Current Version
4 |
5 | v2.1.x
6 |
7 | - Dependency update and changes to improve compatibility with AWS CLI
8 | - Testing with key AWS CLI capabilities, see AWSCLI.md
9 |
10 | ## Previous Versions
11 |
12 | v2.0.0
13 |
14 | - Dependency updates, internal refactor
15 |
16 | v1.5.0
17 |
18 | - Breaking change; signatures no longer being validated
19 | - Dependency updates
20 | - Folder fixes
21 | - Owner information included in enumeration
22 | - Better alerts on startup about request requirements (virtual hosting vs path style URLs)
23 |
24 | v1.4.0
25 |
26 | - Minor refactor
27 | - Fixes to enumeration including folder support
28 | - Request signature authentication
29 |
30 | v1.3.0.1
31 |
32 | - Migrate database layer to ORM
33 | - Improved usability and console log messages
34 | - Simplification of objects
35 | - Centralized authentication and authorization
36 | - Virtualized storage layer to support new backend storage options
37 | - Updated Postman collection
38 | - Dockerfile for containerized deployments
39 |
40 | v1.2.0.2
41 |
42 | - Minor cleanup, version from assembly, dependency update, XML documentation, Postman collection
43 |
44 | v1.2.0
45 |
46 | - Support for bucket in hostname or bucket in URL
47 | - Dependency update
48 |
49 | v1.1.0
50 |
51 | - Dependency update with performance improvements, better async behavior
52 | - Better support for large objects using streams instead of memory-intensive byte arrays
53 | - Better support for chunked transfer-encoding
54 | - Bugfixes
55 |
56 | v1.0.x
57 |
58 | - Added bucket location API
59 | - Changed serializer to remove pretty print for Cyberduck compatibility (S3 Java SDK compatibility)
60 | - Added ACL APIs
61 | - Authentication header support for both v2 and v4
62 | - Chunked transfer support
63 | - Initial release; please see supported APIs below.
64 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | - Using welcoming and inclusive language
12 | - Being respectful of differing viewpoints and experiences
13 | - Gracefully accepting constructive criticism
14 | - Focusing on what is best for the community
15 | - Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | - Trolling, insulting/derogatory comments, and personal or political attacks
21 | - Public or private harassment
22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | - Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
44 |
45 | For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq
46 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thank you for your interest in contributing!
4 |
5 | The following is a set of guidelines for contributing to our project on Github. These are mostly guidelines, not rules.
6 |
7 | ## Code of Conduct
8 |
9 | This project and everyone participating in it is governed by the Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to project moderators.
10 |
11 | ## Pull Requests
12 |
13 | Please follow these guidelines when submitting pull requests (PRs):
14 |
15 | - PRs should be manageable in size to make it easy for us to validate and integrate
16 | - Each PR should be contained to a single fix or a single feature
17 | - Describe the motivation for the PR
18 | - Describe a methodology to test and validate, if appropriate
19 |
20 | Please ensure that the code in your PR follows a style similar to that of the project. If you find a material discrepancy between the style followed by the project and a de facto standard style given the project language and framework, please let us know so we can amend and make our code more maintainable.
21 |
22 | ## Asking Questions
23 |
24 | Prior to asking questions, please review closed issues and wiki pages. If your question is not answered in either of those places, please feel free to file an issue! This will also help us to build out documentation.
25 |
26 | ## Reporting Bugs
27 |
28 | If you encounter an issue, please let us know! We kindly ask that you supply the following information with your bug report when you file an issue. Feel free to copy/paste from the below and use as a template.
29 |
30 | --- Bug Report ---
31 |
32 | Operating system and version: Windows 10
33 | Framework and runtime: .NET Core 2.0
34 | Issue encountered: The widget shifted left
35 | Expected behavior: The widget should have shifted right
36 | Steps to reproduce: Instantiate the widget and call .ShiftRight()
37 | Sample code encapsulating the problem:
38 | ```
39 | Widget widget = new Widget();
40 | widget.ShiftRight();
41 | ```
42 | Exception details: [insert exception output here]
43 | Stack trace: [if appropriate]
44 |
45 | --- End ---
46 |
47 | ## Suggesting Enhancements
48 |
49 | Should there be a way that you feel we could improve upon this project, please feel free to file an issue and use the template below to provide the necessary details to support the request.
50 |
51 | Some basic guidelines for suggesting enhancements:
52 |
53 | - Use a clear and descriptive title for the issue to identify the suggestion.
54 | - Provide a step-by-step description of the suggested enhancement in as many details as possible.
55 | - Provide specific examples to demonstrate the steps including copy/pasteable snippets where possible
56 | - Describe the current behavior and the behavior you would like to see
57 | - Describe the usefulness of the enhancement to yourself and potentially to others
58 |
59 | --- Enhancement Request ---
60 |
61 | Enhancement request title: Widgets should have a color attribute
62 | Use case: I want to specify what color a widget is
63 | Current behavior: Widgets don't have a color
64 | Requested behavior: Allow me to specify a widget's color
65 | Recommended implementation: Add a Color attribute to the Widget class
66 | Usefulness of the enhancement: All widgets have color, and everyone has to build their own implementation to set this
67 |
68 | --- End ---
69 |
--------------------------------------------------------------------------------
/DONATIONS.md:
--------------------------------------------------------------------------------
1 | ## Donations
2 |
3 | If you're interested in financially supporting this work on other open source projects I manage, first of all, thank you! It brings me delight to know that this software has helped you in some way. Please find below address details for donations using
4 |
5 | ### Traditional
6 |
7 | | Method | Address |
8 | |--------|---------|
9 | | PayPal | @joelchristner - https://paypal.me/joelchristner?country.x=US&locale.x=en_US |
10 | | Venmo | @Joel-Christner - https://account.venmo.com/u/Joel-Christner |
11 |
12 | ### Cryptocurrency (Mainstream)
13 |
14 | | Method | Address |
15 | |----------|---------|
16 | | Bitcoin | 3HRgnvEWQBDdWDp75CFDsz3PirCYmftDwU |
17 | | Ethereum | 0xE064dC84270e17e2Ac34b2552461d672BdBC5e36 |
18 |
19 | ### Cryptocurrency (Altcoins)
20 |
21 | | Method | Address |
22 | |--------|---------|
23 | | XRP | Tag 1765608084 Address rw2ciyaNshpHe7bCHo4bRWq6pqqynnWKQg |
24 | | Shiba Inu SHIB | 0xdA58D4ba0d5823d80a0C42C69E139124B889c69a |
25 | | Algorand ALGO | FFKA23KC4BHEU5HM4OQHLEILVIYDLRXYK6WD6UI573JPUGHZR43JVHAF7A |
26 | | Stellar Lumens XML | Memo 2264929895 Address GDQP2KPQGKIHYJGXNUIYOMHARUARCA7DJT5FO2FFOOKY3B2WSQHG4W37 |
27 | | Dogecoin DOGE | DQ2dn4UifpYA8RyuNF1t112Y1XH5L42Rxv |
28 | | Cardano ADA | addr1vxrxyrv0phgr2xcl08dj0sfy425mhqehuu4dy99ja4nhtwqfsvgjk |
29 |
30 | If you have a coin you prefer to use, please let me know!
31 |
--------------------------------------------------------------------------------
/Docker/compose-down.bat:
--------------------------------------------------------------------------------
1 | docker compose -f compose.yaml down
2 |
--------------------------------------------------------------------------------
/Docker/compose-down.sh:
--------------------------------------------------------------------------------
1 | docker compose -f compose.yaml down
2 |
--------------------------------------------------------------------------------
/Docker/compose-up.bat:
--------------------------------------------------------------------------------
1 | docker compose -f compose.yaml up
2 |
--------------------------------------------------------------------------------
/Docker/compose-up.sh:
--------------------------------------------------------------------------------
1 | docker compose -f compose.yaml up &
2 |
--------------------------------------------------------------------------------
/Docker/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | #
4 | # Less3
5 | # See https://hub.docker.com/r/jchristn/less3
6 | # and https://github.com/jchristn/less3
7 | #
8 |
9 | litegraph:
10 | container_name: 'less3'
11 | image: 'jchristn/less3:v2.1.11'
12 | network_mode: 'host'
13 | stdin_open: true
14 | tty: true
15 | volumes:
16 | - ./system.json:/app/system.json
17 | - ./less3.db:/app/less3.db
18 | - ./logs/:/app/logs/
19 | - ./temp/:/app/temp/
20 | - ./disk/:/app/disk/
21 | healthcheck:
22 | test: curl --fail http://localhost:8000
23 |
--------------------------------------------------------------------------------
/Docker/less3.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/Docker/less3.db
--------------------------------------------------------------------------------
/Docker/run.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | IF "%1" == "" GOTO :Usage
4 |
5 | if not exist system.json (
6 | echo Configuration file system.json not found.
7 | exit /b 1
8 | )
9 |
10 | REM Items that require persistence
11 | REM system.json
12 | REM less3.db
13 | REM logs/
14 | REM temp/
15 | REM disk/
16 |
17 | REM Argument order matters!
18 |
19 | docker run ^
20 | -p 8000:8000 ^
21 | -t ^
22 | -i ^
23 | -e "TERM=xterm-256color" ^
24 | -v .\system.json:/app/system.json ^
25 | -v .\less3.db:/app/less3.db ^
26 | -v .\logs\:/app/logs/ ^
27 | -v .\temp\:/app/temp/ ^
28 | -v .\disk\:/app/disk/ ^
29 | jchristn/less3:%1
30 |
31 | GOTO :Done
32 |
33 | :Usage
34 | ECHO Provide one argument indicating the tag.
35 | ECHO Example: dockerrun.bat v2.1.11
36 | :Done
37 | @echo on
38 |
--------------------------------------------------------------------------------
/Docker/run.sh:
--------------------------------------------------------------------------------
1 | if [ -z "${IMG_TAG}" ]; then
2 | IMG_TAG='v2.1.11'
3 | fi
4 |
5 | echo Using image tag $IMG_TAG
6 |
7 | if [ ! -f "system.json" ]
8 | then
9 | echo Configuration file system.json not found.
10 | exit
11 | fi
12 |
13 | # Items that require persistence
14 | # system.json
15 | # less3.db
16 | # logs/
17 | # temp/
18 | # disk/
19 |
20 | # Argument order matters!
21 |
22 | docker run \
23 | -p 8000:8000 \
24 | -t \
25 | -i \
26 | -e "TERM=xterm-256color" \
27 | -v ./system.json:/app/system.json \
28 | -v ./less3.db:/app/less3.db \
29 | -v ./logs/:/app/logs/ \
30 | -v ./temp/:/app/temp/ \
31 | -v ./disk/:/app/disk/ \
32 | jchristn/less3:$IMG_TAG
33 |
34 |
--------------------------------------------------------------------------------
/Docker/system.json:
--------------------------------------------------------------------------------
1 | {
2 | "EnableConsole": true,
3 | "ValidateSignatures": true,
4 | "HeaderApiKey": "x-api-key",
5 | "AdminApiKey": "less3admin",
6 | "RegionString": "us-west-1",
7 | "Database": {
8 | "Filename": "./less3.db",
9 | "Type": "Sqlite",
10 | "Port": 0,
11 | "Debug": {
12 | "EnableForQueries": false,
13 | "EnableForResults": false
14 | }
15 | },
16 | "Webserver": {
17 | "Hostname": "*",
18 | "Port": 8000,
19 | "IO": {
20 | "StreamBufferSize": 65536,
21 | "MaxRequests": 1024,
22 | "ReadTimeoutMs": 10000,
23 | "MaxIncomingHeadersSize": 65536,
24 | "EnableKeepAlive": false
25 | },
26 | "Ssl": {
27 | "Enable": false,
28 | "MutuallyAuthenticate": false,
29 | "AcceptInvalidAcertificates": true
30 | },
31 | "Headers": {
32 | "IncludeContentLength": true,
33 | "DefaultHeaders": {
34 | "Access-Control-Allow-Origin": "*",
35 | "Access-Control-Allow-Methods": "OPTIONS, HEAD, GET, PUT, POST, DELETE, PATCH",
36 | "Access-Control-Allow-Headers": "*",
37 | "Access-Control-Expose-Headers": "",
38 | "Accept": "*/*",
39 | "Accept-Language": "en-US, en",
40 | "Accept-Charset": "ISO-8859-1, utf-8",
41 | "Cache-Control": "no-cache",
42 | "Connection": "close",
43 | "Host": "localhost:8000"
44 | }
45 | },
46 | "AccessControl": {
47 | "DenyList": {},
48 | "PermitList": {},
49 | "Mode": "DefaultPermit"
50 | },
51 | "Debug": {
52 | "AccessControl": false,
53 | "Routing": false,
54 | "Requests": false,
55 | "Responses": false
56 | }
57 | },
58 | "Storage": {
59 | "TempDirectory": "./temp/",
60 | "StorageType": "Disk",
61 | "DiskDirectory": "./disk/"
62 | },
63 | "Logging": {
64 | "SyslogServerIp": "127.0.0.1",
65 | "SyslogServerPort": 514,
66 | "MinimumLevel": "Info",
67 | "LogHttpRequests": false,
68 | "LogS3Requests": false,
69 | "LogExceptions": false,
70 | "LogSignatureValidation": false,
71 | "ConsoleLogging": true,
72 | "DiskLogging": true,
73 | "DiskDirectory": "./logs/"
74 | },
75 | "Debug": {
76 | "Authentication": false,
77 | "S3Requests": false,
78 | "Exceptions": false
79 | }
80 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Less3 :: S3-Compatible Object Storage
4 |
5 | Less3 is an S3-compatible object storage platform that you can run anywhere.
6 |
7 | 
8 |
9 | ## Use Cases
10 |
11 | Core use cases for Less3:
12 |
13 | - Local object storage - S3-compatible storage on your laptop, virtual machine, container, or bare metal
14 | - Private cloud object storage - use your existing private cloud hardware to create an S3-compatible storage pool
15 | - Development and test - local devtest against S3-compatible storage
16 | - Remote storage - deploy S3-compatible storage in environments where you must control data placement
17 |
18 | ## New in This Version
19 |
20 | v2.1.x
21 |
22 | - Dependency update and changes to improve compatibility with AWS CLI
23 | - Testing with key AWS CLI capabilities, see AWSCLI.md
24 |
25 | ## Help and Feedback
26 |
27 | First things first - do you need help or have feedback? Please file an issue here.
28 |
29 | ## Special Thanks
30 |
31 | Thanks to @iain-cyborn for helping make the platform better!
32 |
33 | ## Initial Setup
34 |
35 | The binaries for Less3 can be created by compiling from source. Executing the binary will create a system configuration in the ```system.json``` file along with the configuration database ```less3.db```.
36 |
37 | The ```Server.DnsHostname``` MUST be set to a hostname. You cannot use IP addresses (parsing will fail). Incoming HTTP requests must have a HOST header value that matches the value in ```Server.DnsHostname```. If it does not match, you will receive a ```400/Bad Request```.
38 |
39 | If you use ```*```, ```+```, or ```0.0.0.0``` for the ```Server.DnsHostname```, Less3 must be executed using administrative privileges (this is required by the underlying operating system).
40 |
41 | To get started, clone Less3, build, publish, and run!
42 |
43 | ```
44 | $ git clone https://github.com/jchristn/less3
45 | $ cd less3
46 | $ dotnet build -f netcoreapp2.2
47 | $ dotnet publish -f netcoreapp2.2
48 | $ cd less3/bin/debug/netcoreapp2.2/publish
49 | $ dotnet less3.dll
50 | ```
51 |
52 | ## S3 Client Compatibility
53 |
54 | Less3 was designed to be consumed using either the AWS SDK or direct RESTful integration in accordance with Amazon's official documentation (https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html). Should you encounter a discrepancy between how Less3 operates and how AWS S3 operates, please file an issue.
55 |
56 | I tested Less3 using the AWS SDK for C#, a live account on S3, CloudBerry Explorer for S3 (see https://www.cloudberrylab.com/explorer/windows/amazon-s3.aspx), and S3 Browser (see http://s3browser.com/). If you have or recommend other tools, please file an issue here and let me know!
57 |
58 | ## Supported APIs
59 |
60 | Please refer to the compatibility matrix found in 'assets' for a full list of supported APIs and caveats.
61 |
62 | The following APIs are supported with Less3:
63 |
64 | - Service APIs
65 | - ListBuckets
66 |
67 | - Bucket APIs
68 | - Write
69 | - WriteAcl
70 | - WriteTagging
71 | - WriteVersioning (no MFA delete support)
72 | - Delete
73 | - DeleteTagging
74 | - Exists
75 | - Read (list objects v2)
76 | - ReadAcl
77 | - ReadVersions
78 | - ReadTagging
79 |
80 | - Object APIs
81 | - Write
82 | - WriteAcl
83 | - WriteTagging
84 | - Delete
85 | - DeleteMultiple
86 | - DeleteTagging
87 | - Exists
88 | - Read
89 | - ReadAcl
90 | - ReadRange
91 | - ReadTagging
92 |
93 | ## API Support
94 |
95 | There are several minor differences between how S3 and less3 handle certain aspects of API requests. However, these should be inconsequential from the perspective of the developer (for instance, version IDs are numbers internally within less3 rather than strings).
96 |
97 | Should you find any incompatibilities or behavioral issues with the APIs listed above that are considered 'supported', please file an issue here along with details on the expected behavior. I've tried to mimic the behavior of S3 while building out the API logic. A link to the supporting documentation will also be helpful to aid me in righting the wrong :)
98 |
99 | ## Bucket in Hostname vs URL
100 |
101 | Less3 supports cases where having the bucket name as:
102 | - **Path style URLs** - the bucket name is part of the URL (```http://[hostname]/[bucket]/[key]```)
103 | - **Virtual hosted URLs** - the bucket name is part of the hostname (```http://[bucket].[hostname]/[key]```)
104 |
105 | To use **path style URLs** do **not** set ```Server.BaseDomain```. This is the default configuration.
106 |
107 | To use **virtual hosted URLs**, you must:
108 |
109 | - Set ```Server.BaseDomain``` - if your hostname is ```localhost```, set this value to ```.localhost``` (prepend with a period)
110 | - Set ```Server.DnsHostname``` to ```*```
111 | - Run Less3 as administrator
112 | - Ensure your hosted hostnames (i.e. ```[bucket].[hostname]```) are resolvable through DNS to your machine
113 |
114 | ## Administrative APIs
115 |
116 | Please refer to the 'wiki' for helpful notes including how to use the administrative APIs.
117 |
118 | ## Open Source Packages
119 |
120 | Less3 is built using a series of open-source packages, including:
121 |
122 | - AWS SDK - https://github.com/aws/aws-sdk-net
123 | - S3 Server - https://github.com/jchristn/s3server
124 | - Watson Webserver - https://github.com/jchristn/WatsonWebserver
125 | - WatsonORM - https://github.com/jchristn/watsonorm
126 |
127 | ## Deployment in Docker
128 |
129 | Less3 is available in [DockerHub](https://hub.docker.com/r/jchristn/less3). Refer to the `Docker` directory for the `compose.yaml`, `system.json`, and `less3.db` files that you will need. By default, the node will listen on TCP port `8000` and you can use the access key `default` and secret key `default`. Be sure to connect without SSL, and use path-style URLs.
130 |
131 | ## Version History
132 |
133 | Refer to CHANGELOG.md for details.
134 |
--------------------------------------------------------------------------------
/assets/CompatibilityTests.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/CompatibilityTests.xlsx
--------------------------------------------------------------------------------
/assets/diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/diagram.png
--------------------------------------------------------------------------------
/assets/diagram.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/diagram.pptx
--------------------------------------------------------------------------------
/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/favicon.ico
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/favicon.png
--------------------------------------------------------------------------------
/assets/heart.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/heart.ico
--------------------------------------------------------------------------------
/assets/heart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/heart.png
--------------------------------------------------------------------------------
/assets/heart.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/heart.webp
--------------------------------------------------------------------------------
/assets/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/icon.ico
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/assets/logo.png
--------------------------------------------------------------------------------
/cyberduck/Less3-pathstyle-http.cyberduckprofile:
--------------------------------------------------------------------------------
1 |
2 |
3 | Protocol
4 | s3
5 | Vendor
6 | s3-path-style
7 | Scheme
8 | http
9 | Description
10 | S3 (Deprecated path style requests)
11 | Hostname Configurable
12 |
13 | Port Configurable
14 |
15 | Username Configurable
16 |
17 | Properties
18 |
19 | s3.bucket.virtualhost.disable=true
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/cyberduck/Less3-virtualhosted.cyberduckprofile:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Protocol
6 | s3
7 | Vendor
8 | s3-http
9 | Scheme
10 | http
11 | Description
12 | Less3
13 | Default Port
14 | 8000
15 | Hostname Configurable
16 |
17 | Port Configurable
18 |
19 | Username Configurable
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/Less3.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.28307.168
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Less3", "Less3\Less3.csproj", "{A6AF80E6-A92A-4A52-8ECD-713EFD3465DA}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {A6AF80E6-A92A-4A52-8ECD-713EFD3465DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {A6AF80E6-A92A-4A52-8ECD-713EFD3465DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {A6AF80E6-A92A-4A52-8ECD-713EFD3465DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {A6AF80E6-A92A-4A52-8ECD-713EFD3465DA}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {A039530E-9907-434C-8B92-D1CE3B972E9B}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/src/Less3/Api/Admin/AdminApiHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Api.Admin
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | using S3ServerLibrary;
11 | using SyslogLogging;
12 | using WatsonWebserver;
13 | using WatsonWebserver.Core;
14 |
15 | using Less3.Classes;
16 |
17 | ///
18 | /// Admin API handler.
19 | ///
20 | public class AdminApiHandler
21 | {
22 | #region Public-Members
23 |
24 | #endregion
25 |
26 | #region Private-Members
27 |
28 | private Settings _Settings;
29 | private LoggingModule _Logging;
30 | private ConfigManager _Config;
31 | private BucketManager _Buckets;
32 | private AuthManager _Auth;
33 |
34 | private GetHandler _GetHandler;
35 | private PostHandler _PostHandler;
36 | private DeleteHandler _DeleteHandler;
37 |
38 | #endregion
39 |
40 | #region Constructors-and-Factories
41 |
42 | internal AdminApiHandler(
43 | Settings settings,
44 | LoggingModule logging,
45 | ConfigManager config,
46 | BucketManager buckets,
47 | AuthManager auth)
48 | {
49 | if (settings == null) throw new ArgumentNullException(nameof(settings));
50 | if (logging == null) throw new ArgumentNullException(nameof(logging));
51 | if (config == null) throw new ArgumentNullException(nameof(config));
52 | if (buckets == null) throw new ArgumentNullException(nameof(buckets));
53 | if (auth == null) throw new ArgumentNullException(nameof(auth));
54 |
55 | _Settings = settings;
56 | _Logging = logging;
57 | _Config = config;
58 | _Buckets = buckets;
59 | _Auth = auth;
60 |
61 | _GetHandler = new GetHandler(_Settings, _Logging, _Config, _Buckets, _Auth);
62 | _PostHandler = new PostHandler(_Settings, _Logging, _Config, _Buckets, _Auth);
63 | _DeleteHandler = new DeleteHandler(_Settings, _Logging, _Config, _Buckets, _Auth);
64 | }
65 |
66 | #endregion
67 |
68 | #region Internal-Methods
69 |
70 | internal async Task Process(S3Context ctx)
71 | {
72 | switch (ctx.Http.Request.Method)
73 | {
74 | case WatsonWebserver.Core.HttpMethod.GET:
75 | await _GetHandler.Process(ctx);
76 | return;
77 | case WatsonWebserver.Core.HttpMethod.POST:
78 | await _PostHandler.Process(ctx);
79 | return;
80 | case WatsonWebserver.Core.HttpMethod.DELETE:
81 | await _DeleteHandler.Process(ctx);
82 | return;
83 | }
84 |
85 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
86 | return;
87 | }
88 |
89 | #endregion
90 |
91 | #region Private-Methods
92 |
93 | #endregion
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Less3/Api/Admin/DeleteHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Api.Admin
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | using S3ServerLibrary;
11 | using SyslogLogging;
12 |
13 | using Less3.Classes;
14 |
15 | ///
16 | /// Admin API DELETE handler.
17 | ///
18 | public class DeleteHandler
19 | {
20 | #region Public-Members
21 |
22 | #endregion
23 |
24 | #region Private-Members
25 |
26 | private Settings _Settings;
27 | private LoggingModule _Logging;
28 | private ConfigManager _Config;
29 | private BucketManager _Buckets;
30 | private AuthManager _Auth;
31 |
32 | #endregion
33 |
34 | #region Constructors-and-Factories
35 |
36 | internal DeleteHandler(
37 | Settings settings,
38 | LoggingModule logging,
39 | ConfigManager config,
40 | BucketManager buckets,
41 | AuthManager auth)
42 | {
43 | if (settings == null) throw new ArgumentNullException(nameof(settings));
44 | if (logging == null) throw new ArgumentNullException(nameof(logging));
45 | if (config == null) throw new ArgumentNullException(nameof(config));
46 | if (buckets == null) throw new ArgumentNullException(nameof(buckets));
47 | if (auth == null) throw new ArgumentNullException(nameof(auth));
48 |
49 | _Settings = settings;
50 | _Logging = logging;
51 | _Config = config;
52 | _Buckets = buckets;
53 | _Auth = auth;
54 | }
55 |
56 | #endregion
57 |
58 | #region Internal-Methods
59 |
60 | internal async Task Process(S3Context ctx)
61 | {
62 | if (ctx.Http.Request.Url.Elements[1].Equals("buckets"))
63 | {
64 | await DeleteBuckets(ctx);
65 | return;
66 | }
67 | else if (ctx.Http.Request.Url.Elements[1].Equals("users"))
68 | {
69 | await DeleteUsers(ctx);
70 | return;
71 | }
72 | else if (ctx.Http.Request.Url.Elements[1].Equals("credentials"))
73 | {
74 | await DeleteCredentials(ctx);
75 | return;
76 | }
77 |
78 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
79 | }
80 |
81 | #endregion
82 |
83 | #region Private-Methods
84 |
85 | private async Task DeleteBuckets(S3Context ctx)
86 | {
87 | if (ctx.Http.Request.Url.Elements.Length != 3)
88 | {
89 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
90 | return;
91 | }
92 |
93 | Bucket bucket = _Config.GetBucketByGuid(ctx.Http.Request.Url.Elements[2]);
94 | if (bucket == null)
95 | {
96 | ctx.Response.StatusCode = 404;
97 | ctx.Response.ContentType = "text/plain";
98 | await ctx.Response.Send();
99 | return;
100 | }
101 |
102 | bool destroy = false;
103 | if (ctx.Http.Request.Query.Elements.AllKeys.Contains("destroy")) destroy = true;
104 | _Buckets.Remove(bucket, destroy);
105 |
106 | ctx.Response.StatusCode = 204;
107 | ctx.Response.ContentType = "text/plain";
108 | await ctx.Response.Send();
109 | return;
110 | }
111 |
112 | private async Task DeleteUsers(S3Context ctx)
113 | {
114 | if (ctx.Http.Request.Url.Elements.Length != 3)
115 | {
116 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
117 | return;
118 | }
119 |
120 | User user = _Config.GetUserByGuid(ctx.Http.Request.Url.Elements[2]);
121 | if (user == null)
122 | {
123 | ctx.Response.StatusCode = 404;
124 | ctx.Response.ContentType = "text/plain";
125 | await ctx.Response.Send();
126 | return;
127 | }
128 |
129 | _Config.DeleteUser(user.GUID);
130 |
131 | ctx.Response.StatusCode = 204;
132 | ctx.Response.ContentType = "text/plain";
133 | await ctx.Response.Send();
134 | return;
135 | }
136 |
137 | private async Task DeleteCredentials(S3Context ctx)
138 | {
139 | if (ctx.Http.Request.Url.Elements.Length != 3)
140 | {
141 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
142 | return;
143 | }
144 |
145 | Credential cred = _Config.GetCredentialByGuid(ctx.Http.Request.Url.Elements[2]);
146 | if (cred == null)
147 | {
148 | ctx.Response.StatusCode = 404;
149 | ctx.Response.ContentType = "text/plain";
150 | await ctx.Response.Send();
151 | return;
152 | }
153 |
154 | _Config.DeleteCredential(cred.GUID);
155 |
156 | ctx.Response.StatusCode = 204;
157 | ctx.Response.ContentType = "text/plain";
158 | await ctx.Response.Send();
159 | return;
160 | }
161 |
162 | #endregion
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/Less3/Api/Admin/GetHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Api.Admin
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | using S3ServerLibrary;
11 | using SyslogLogging;
12 |
13 | using Less3.Classes;
14 |
15 | ///
16 | /// Admin API GET handler.
17 | ///
18 | internal class GetHandler
19 | {
20 | #region Public-Members
21 |
22 | #endregion
23 |
24 | #region Private-Members
25 |
26 | private Settings _Settings;
27 | private LoggingModule _Logging;
28 | private ConfigManager _Config;
29 | private BucketManager _Buckets;
30 | private AuthManager _Auth;
31 |
32 | #endregion
33 |
34 | #region Constructors-and-Factories
35 |
36 | internal GetHandler(
37 | Settings settings,
38 | LoggingModule logging,
39 | ConfigManager config,
40 | BucketManager buckets,
41 | AuthManager auth)
42 | {
43 | if (settings == null) throw new ArgumentNullException(nameof(settings));
44 | if (logging == null) throw new ArgumentNullException(nameof(logging));
45 | if (config == null) throw new ArgumentNullException(nameof(config));
46 | if (buckets == null) throw new ArgumentNullException(nameof(buckets));
47 | if (auth == null) throw new ArgumentNullException(nameof(auth));
48 |
49 | _Settings = settings;
50 | _Logging = logging;
51 | _Config = config;
52 | _Buckets = buckets;
53 | _Auth = auth;
54 | }
55 |
56 | #endregion
57 |
58 | #region Internal-Methods
59 |
60 | internal async Task Process(S3Context ctx)
61 | {
62 | if (ctx.Http.Request.Url.Elements[1].Equals("buckets"))
63 | {
64 | await GetBuckets(ctx);
65 | return;
66 | }
67 | else if (ctx.Http.Request.Url.Elements[1].Equals("users"))
68 | {
69 | await GetUsers(ctx);
70 | return;
71 | }
72 | else if (ctx.Http.Request.Url.Elements[1].Equals("credentials"))
73 | {
74 | await GetCredentials(ctx);
75 | return;
76 | }
77 |
78 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
79 | }
80 |
81 | #endregion
82 |
83 | #region Private-Methods
84 |
85 | private async Task GetBuckets(S3Context ctx)
86 | {
87 | if (ctx.Http.Request.Url.Elements.Length >= 3)
88 | {
89 | Bucket bucket = _Buckets.GetByGuid(ctx.Http.Request.Url.Elements[2]);
90 | if (bucket == null)
91 | {
92 | ctx.Response.StatusCode = 404;
93 | ctx.Response.ContentType = "text/plain";
94 | await ctx.Response.Send();
95 | return;
96 | }
97 | else
98 | {
99 | ctx.Response.StatusCode = 200;
100 | ctx.Response.ContentType = "application/json";
101 | await ctx.Response.Send(SerializationHelper.SerializeJson(bucket, true));
102 | return;
103 | }
104 | }
105 | else
106 | {
107 | List buckets = _Config.GetBuckets();
108 | ctx.Response.StatusCode = 200;
109 | ctx.Response.ContentType = "application/json";
110 | await ctx.Response.Send(SerializationHelper.SerializeJson(buckets, true));
111 | return;
112 | }
113 | }
114 |
115 | private async Task GetUsers(S3Context ctx)
116 | {
117 | if (ctx.Http.Request.Url.Elements.Length >= 3)
118 | {
119 | User user = _Config.GetUserByGuid(ctx.Http.Request.Url.Elements[2]);
120 | if (user == null)
121 | {
122 | ctx.Response.StatusCode = 404;
123 | ctx.Response.ContentType = "text/plain";
124 | await ctx.Response.Send();
125 | return;
126 | }
127 | else
128 | {
129 | ctx.Response.StatusCode = 200;
130 | ctx.Response.ContentType = "application/json";
131 | await ctx.Response.Send(SerializationHelper.SerializeJson(user, true));
132 | return;
133 | }
134 | }
135 | else
136 | {
137 | List users = _Config.GetUsers();
138 | ctx.Response.StatusCode = 200;
139 | ctx.Response.ContentType = "application/json";
140 | await ctx.Response.Send(SerializationHelper.SerializeJson(users, true));
141 | return;
142 | }
143 | }
144 |
145 | private async Task GetCredentials(S3Context ctx)
146 | {
147 | if (ctx.Http.Request.Url.Elements.Length >= 3)
148 | {
149 | Credential cred = _Config.GetCredentialByGuid(ctx.Http.Request.Url.Elements[2]);
150 | if (cred == null)
151 | {
152 | ctx.Response.StatusCode = 404;
153 | ctx.Response.ContentType = "text/plain";
154 | await ctx.Response.Send();
155 | return;
156 | }
157 | else
158 | {
159 | ctx.Response.StatusCode = 200;
160 | ctx.Response.ContentType = "application/json";
161 | await ctx.Response.Send(SerializationHelper.SerializeJson(cred, true));
162 | return;
163 | }
164 | }
165 | else
166 | {
167 | List creds = _Config.GetCredentials();
168 | ctx.Response.StatusCode = 200;
169 | ctx.Response.ContentType = "application/json";
170 | await ctx.Response.Send(SerializationHelper.SerializeJson(creds, true));
171 | return;
172 | }
173 | }
174 |
175 | #endregion
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/Less3/Api/Admin/PostHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Api.Admin
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | using S3ServerLibrary;
11 | using SyslogLogging;
12 |
13 | using Less3.Classes;
14 |
15 | ///
16 | /// Admin API POST handler.
17 | ///
18 | internal class PostHandler
19 | {
20 | #region Public-Members
21 |
22 | #endregion
23 |
24 | #region Private-Members
25 |
26 | private Settings _Settings;
27 | private LoggingModule _Logging;
28 | private ConfigManager _Config;
29 | private BucketManager _Buckets;
30 | private AuthManager _Auth;
31 |
32 | #endregion
33 |
34 | #region Constructors-and-Factories
35 |
36 | internal PostHandler(
37 | Settings settings,
38 | LoggingModule logging,
39 | ConfigManager config,
40 | BucketManager buckets,
41 | AuthManager auth)
42 | {
43 | if (settings == null) throw new ArgumentNullException(nameof(settings));
44 | if (logging == null) throw new ArgumentNullException(nameof(logging));
45 | if (config == null) throw new ArgumentNullException(nameof(config));
46 | if (buckets == null) throw new ArgumentNullException(nameof(buckets));
47 | if (auth == null) throw new ArgumentNullException(nameof(auth));
48 |
49 | _Settings = settings;
50 | _Logging = logging;
51 | _Config = config;
52 | _Buckets = buckets;
53 | _Auth = auth;
54 | }
55 |
56 | #endregion
57 |
58 | #region Internal-Methods
59 |
60 | internal async Task Process(S3Context ctx)
61 | {
62 | if (ctx.Http.Request.Url.Elements[1].Equals("buckets"))
63 | {
64 | await PostBuckets(ctx);
65 | return;
66 | }
67 | else if (ctx.Http.Request.Url.Elements[1].Equals("users"))
68 | {
69 | await PostUsers(ctx);
70 | return;
71 | }
72 | else if (ctx.Http.Request.Url.Elements[1].Equals("credentials"))
73 | {
74 | await PostCredentials(ctx);
75 | return;
76 | }
77 |
78 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
79 | }
80 |
81 | #endregion
82 |
83 | #region Private-Methods
84 |
85 | private async Task PostBuckets(S3Context ctx)
86 | {
87 | if (ctx.Http.Request.Url.Elements.Length != 2)
88 | {
89 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
90 | return;
91 | }
92 |
93 | byte[] data = null;
94 | Bucket bucket = null;
95 |
96 | try
97 | {
98 | data = Common.StreamToBytes(ctx.Request.Data);
99 | bucket = SerializationHelper.DeserializeJson(Encoding.UTF8.GetString(data));
100 | }
101 | catch (Exception)
102 | {
103 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
104 | return;
105 | }
106 |
107 | Bucket tempBucket = _Config.GetBucketByName(bucket.Name);
108 | if (tempBucket != null)
109 | {
110 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.BucketAlreadyExists);
111 | return;
112 | }
113 |
114 | _Buckets.Add(bucket);
115 |
116 | ctx.Response.StatusCode = 201;
117 | ctx.Response.ContentType = "text/plain";
118 | await ctx.Response.Send();
119 | }
120 |
121 | private async Task PostUsers(S3Context ctx)
122 | {
123 | if (ctx.Http.Request.Url.Elements.Length != 2)
124 | {
125 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
126 | return;
127 | }
128 |
129 | User user = null;
130 |
131 | try
132 | {
133 | user = SerializationHelper.DeserializeJson(ctx.Request.DataAsString);
134 | }
135 | catch (Exception)
136 | {
137 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
138 | return;
139 | }
140 |
141 | User tempUser = _Config.GetUserByEmail(user.Email);
142 | if (tempUser != null)
143 | {
144 | ctx.Response.StatusCode = 409;
145 | ctx.Response.ContentType = "text/plain";
146 | await ctx.Response.Send();
147 | return;
148 | }
149 |
150 | tempUser = _Config.GetUserByGuid(user.GUID);
151 | if (tempUser != null)
152 | {
153 | ctx.Response.StatusCode = 409;
154 | ctx.Response.ContentType = "text/plain";
155 | await ctx.Response.Send();
156 | return;
157 | }
158 |
159 | _Config.AddUser(user);
160 |
161 | ctx.Response.StatusCode = 201;
162 | ctx.Response.ContentType = "text/plain";
163 | await ctx.Response.Send();
164 | }
165 |
166 | private async Task PostCredentials(S3Context ctx)
167 | {
168 | if (ctx.Http.Request.Url.Elements.Length != 2)
169 | {
170 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
171 | return;
172 | }
173 |
174 | byte[] data = null;
175 | Credential cred = null;
176 |
177 | try
178 | {
179 | data = Common.StreamToBytes(ctx.Request.Data);
180 | cred = SerializationHelper.DeserializeJson(Encoding.UTF8.GetString(data));
181 | }
182 | catch (Exception)
183 | {
184 | await ctx.Response.Send(S3ServerLibrary.S3Objects.ErrorCode.InvalidRequest);
185 | return;
186 | }
187 |
188 | Credential tempCred = _Config.GetCredentialByAccessKey(cred.AccessKey);
189 | if (tempCred != null)
190 | {
191 | ctx.Response.StatusCode = 409;
192 | ctx.Response.ContentType = "text/plain";
193 | await ctx.Response.Send();
194 | return;
195 | }
196 |
197 | _Config.AddCredential(cred);
198 |
199 | ctx.Response.StatusCode = 201;
200 | ctx.Response.ContentType = "text/plain";
201 | await ctx.Response.Send();
202 | }
203 |
204 | #endregion
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/Less3/Api/S3/ApiHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Api.S3
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | using S3ServerLibrary;
10 | using S3ServerLibrary.S3Objects;
11 |
12 | using SyslogLogging;
13 |
14 | using Less3.Classes;
15 |
16 | ///
17 | /// API handler.
18 | ///
19 | internal class ApiHandler
20 | {
21 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
22 |
23 | #region Public-Members
24 |
25 | #endregion
26 |
27 | #region Private-Members
28 |
29 | private Settings _Settings;
30 | private LoggingModule _Logging;
31 | private ConfigManager _Config;
32 | private BucketManager _Buckets;
33 | private AuthManager _Auth;
34 |
35 | private ServiceHandler _ServiceHandler;
36 | private BucketHandler _BucketHandler;
37 | private ObjectHandler _ObjectHandler;
38 |
39 | #endregion
40 |
41 | #region Constructors-and-Factories
42 |
43 | internal ApiHandler(
44 | Settings settings,
45 | LoggingModule logging,
46 | ConfigManager config,
47 | BucketManager buckets,
48 | AuthManager auth)
49 | {
50 | if (settings == null) throw new ArgumentNullException(nameof(settings));
51 | if (logging == null) throw new ArgumentNullException(nameof(logging));
52 | if (config == null) throw new ArgumentNullException(nameof(config));
53 | if (buckets == null) throw new ArgumentNullException(nameof(buckets));
54 | if (auth == null) throw new ArgumentNullException(nameof(auth));
55 |
56 | _Settings = settings;
57 | _Logging = logging;
58 | _Config = config;
59 | _Buckets = buckets;
60 | _Auth = auth;
61 |
62 | _ServiceHandler = new ServiceHandler(_Settings, _Logging, _Config, _Buckets, _Auth);
63 | _BucketHandler = new BucketHandler(_Settings, _Logging, _Config, _Buckets, _Auth);
64 | _ObjectHandler = new ObjectHandler(_Settings, _Logging, _Config, _Buckets, _Auth);
65 | }
66 |
67 | #endregion
68 |
69 | #region Internal-Methods
70 |
71 | #region Service-APIs
72 |
73 | internal async Task ServiceListBuckets(S3Context ctx)
74 | {
75 | return await _ServiceHandler.ListBuckets(ctx);
76 | }
77 |
78 | internal async Task ServiceExists(S3Context ctx)
79 | {
80 | return _Settings.RegionString;
81 | }
82 |
83 | internal string FindMatchingBaseDomain(string hostname)
84 | {
85 | if (String.IsNullOrEmpty(hostname)) return null;
86 | if (String.IsNullOrEmpty(_Settings.BaseDomain)) return null;
87 |
88 | if (hostname.Equals(_Settings.BaseDomain)) return _Settings.BaseDomain;
89 |
90 | string testDomain = "." + _Settings.BaseDomain;
91 | if (hostname.EndsWith(testDomain)) return _Settings.BaseDomain;
92 |
93 | throw new KeyNotFoundException("A base domain could not be found for hostname '" + hostname + "'.");
94 | }
95 |
96 | #endregion
97 |
98 | #region Bucket-APIs
99 |
100 | internal async Task BucketDelete(S3Context ctx)
101 | {
102 | await _BucketHandler.Delete(ctx);
103 | }
104 |
105 | internal async Task BucketDeleteTagging(S3Context ctx)
106 | {
107 | await _BucketHandler.DeleteTags(ctx);
108 | }
109 |
110 | internal async Task BucketExists(S3Context ctx)
111 | {
112 | return await _BucketHandler.Exists(ctx);
113 | }
114 |
115 | internal async Task BucketReadLocation(S3Context ctx)
116 | {
117 | return await _BucketHandler.ReadLocation(ctx);
118 | }
119 |
120 | internal async Task BucketRead(S3Context ctx)
121 | {
122 | return await _BucketHandler.Read(ctx);
123 | }
124 |
125 | internal async Task BucketReadAcl(S3Context ctx)
126 | {
127 | return await _BucketHandler.ReadAcl(ctx);
128 | }
129 |
130 | internal async Task BucketReadTagging(S3Context ctx)
131 | {
132 | return await _BucketHandler.ReadTags(ctx);
133 | }
134 |
135 | internal async Task BucketReadVersions(S3Context ctx)
136 | {
137 | return await _BucketHandler.ReadVersions(ctx);
138 | }
139 |
140 | internal async Task BucketReadVersioning(S3Context ctx)
141 | {
142 | return await _BucketHandler.ReadVersioning(ctx);
143 | }
144 |
145 | internal async Task BucketWrite(S3Context ctx)
146 | {
147 | await _BucketHandler.Write(ctx);
148 | }
149 |
150 | internal async Task BucketWriteAcl(S3Context ctx, AccessControlPolicy acp)
151 | {
152 | await _BucketHandler.WriteAcl(ctx, acp);
153 | }
154 |
155 | internal async Task BucketWriteTagging(S3Context ctx, Tagging tagging)
156 | {
157 | await _BucketHandler.WriteTagging(ctx, tagging);
158 | }
159 |
160 | internal async Task BucketWriteVersioning(S3Context ctx, VersioningConfiguration versioning)
161 | {
162 | await _BucketHandler.WriteVersioning(ctx, versioning);
163 | }
164 |
165 | #endregion
166 |
167 | #region Object-APIs
168 |
169 | internal async Task ObjectDelete(S3Context ctx)
170 | {
171 | await _ObjectHandler.Delete(ctx);
172 | }
173 |
174 | internal async Task ObjectDeleteMultiple(S3Context ctx, DeleteMultiple dm)
175 | {
176 | return await _ObjectHandler.DeleteMultiple(ctx, dm);
177 | }
178 |
179 | internal async Task ObjectDeleteTagging(S3Context ctx)
180 | {
181 | await _ObjectHandler.DeleteTags(ctx);
182 | }
183 |
184 | internal async Task ObjectExists(S3Context ctx)
185 | {
186 | return await _ObjectHandler.Exists(ctx);
187 | }
188 |
189 | internal async Task ObjectRead(S3Context ctx)
190 | {
191 | return await _ObjectHandler.Read(ctx);
192 | }
193 |
194 | internal async Task ObjectReadAcl(S3Context ctx)
195 | {
196 | return await _ObjectHandler.ReadAcl(ctx);
197 | }
198 |
199 | internal async Task ObjectReadRange(S3Context ctx)
200 | {
201 | return await _ObjectHandler.ReadRange(ctx);
202 | }
203 |
204 | internal async Task ObjectReadTagging(S3Context ctx)
205 | {
206 | return await _ObjectHandler.ReadTags(ctx);
207 | }
208 |
209 | internal async Task ObjectWrite(S3Context ctx)
210 | {
211 | await _ObjectHandler.Write(ctx);
212 | }
213 |
214 | internal async Task ObjectWriteAcl(S3Context ctx, AccessControlPolicy acp)
215 | {
216 | await _ObjectHandler.WriteAcl(ctx, acp);
217 | }
218 |
219 | internal async Task ObjectWriteTagging(S3Context ctx, Tagging tagging)
220 | {
221 | await _ObjectHandler.WriteTagging(ctx, tagging);
222 | }
223 |
224 | #endregion
225 |
226 | #endregion
227 |
228 | #region Private-Methods
229 |
230 | private string AmazonTimestamp(DateTime dt)
231 | {
232 | return dt.ToString("yyyy-MM-ddTHH:mm:ss.fffz");
233 | }
234 |
235 | #endregion
236 |
237 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/src/Less3/Api/S3/ApiHelper.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Api.S3
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using S3ServerLibrary;
8 | using Less3.Classes;
9 |
10 | internal static class ApiHelper
11 | {
12 | internal static RequestMetadata GetRequestMetadata(S3Context ctx)
13 | {
14 | if (ctx == null) return null;
15 | if (ctx.Metadata == null) return null;
16 | return (RequestMetadata)(ctx.Metadata);
17 | }
18 |
19 | internal static string AmazonTimestamp(DateTime dt)
20 | {
21 | return dt.ToString("yyyy-MM-ddTHH:mm:ss.fffz");
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Less3/Api/S3/ServiceHandler.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Api.S3
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | using SyslogLogging;
10 | using S3ServerLibrary;
11 | using S3ServerLibrary.S3Objects;
12 |
13 | using Less3.Classes;
14 |
15 | ///
16 | /// Service APIs.
17 | ///
18 | public class ServiceHandler
19 | {
20 | #region Public-Members
21 |
22 | #endregion
23 |
24 | #region Private-Members
25 |
26 | private Settings _Settings = null;
27 | private LoggingModule _Logging = null;
28 | private ConfigManager _Config = null;
29 | private BucketManager _Buckets = null;
30 | private AuthManager _Auth = null;
31 |
32 | #endregion
33 |
34 | #region Constructors-and-Factories
35 |
36 | internal ServiceHandler(
37 | Settings settings,
38 | LoggingModule logging,
39 | ConfigManager config,
40 | BucketManager buckets,
41 | AuthManager auth)
42 | {
43 | if (settings == null) throw new ArgumentNullException(nameof(settings));
44 | if (logging == null) throw new ArgumentNullException(nameof(logging));
45 | if (config == null) throw new ArgumentNullException(nameof(config));
46 | if (buckets == null) throw new ArgumentNullException(nameof(buckets));
47 | if (auth == null) throw new ArgumentNullException(nameof(auth));
48 |
49 | _Settings = settings;
50 | _Logging = logging;
51 | _Config = config;
52 | _Buckets = buckets;
53 | _Auth = auth;
54 | }
55 |
56 | #endregion
57 |
58 | #region Internal-Methods
59 |
60 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
61 | internal async Task ListBuckets(S3Context ctx)
62 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
63 | {
64 | string header = "[" + ctx.Http.Request.Source.IpAddress + ":" + ctx.Http.Request.Source.Port + " " + ctx.Request.RequestType.ToString() + "] ";
65 |
66 | RequestMetadata md = ApiHelper.GetRequestMetadata(ctx);
67 | if (md == null)
68 | {
69 | _Logging.Warn(header + "unable to retrieve metadata");
70 | throw new S3Exception(new Error(ErrorCode.InternalError));
71 | }
72 |
73 | if (md.Authentication != AuthenticationResult.Authenticated)
74 | {
75 | _Logging.Warn(header + "requestor not authenticated");
76 | throw new S3Exception(new Error(ErrorCode.AccessDenied));
77 | }
78 | else
79 | {
80 | md.Authorization = AuthorizationResult.PermitService;
81 | }
82 |
83 | List buckets = _Buckets.GetUserBuckets(md.User.GUID);
84 |
85 | ListAllMyBucketsResult listBucketsResult = new ListAllMyBucketsResult();
86 | listBucketsResult.Owner = new S3ServerLibrary.S3Objects.Owner();
87 | listBucketsResult.Owner.DisplayName = md.User.Name;
88 | listBucketsResult.Owner.ID = md.User.Name;
89 |
90 | listBucketsResult.Buckets = new Buckets();
91 | listBucketsResult.Buckets.BucketList = new List();
92 |
93 | foreach (Classes.Bucket curr in buckets)
94 | {
95 | S3ServerLibrary.S3Objects.Bucket b = new S3ServerLibrary.S3Objects.Bucket();
96 | b.Name = curr.Name;
97 | b.CreationDate = curr.CreatedUtc;
98 | listBucketsResult.Buckets.BucketList.Add(b);
99 | }
100 |
101 | return listBucketsResult;
102 | }
103 |
104 | #endregion
105 |
106 | #region Private-Methods
107 |
108 | #endregion
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Less3/Assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/src/Less3/Assets/favicon.ico
--------------------------------------------------------------------------------
/src/Less3/Assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/src/Less3/Assets/favicon.png
--------------------------------------------------------------------------------
/src/Less3/Classes/AuthenticationResult.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Text.Json;
7 | using System.Text.Json.Serialization;
8 | using System.Runtime.Serialization;
9 |
10 | ///
11 | /// Authentication result.
12 | ///
13 | [JsonConverter(typeof(JsonStringEnumConverter))]
14 | public enum AuthenticationResult
15 | {
16 | ///
17 | /// No authentication material was supplied.
18 | ///
19 | [EnumMember(Value = "NoMaterialSupplied")]
20 | NoMaterialSupplied,
21 | ///
22 | /// The user was not found.
23 | ///
24 | [EnumMember(Value = "UserNotFound")]
25 | UserNotFound,
26 | ///
27 | /// The supplied access key was not found.
28 | ///
29 | [EnumMember(Value = "AccessKeyNotFound")]
30 | AccessKeyNotFound,
31 | ///
32 | /// Authentication was successful.
33 | ///
34 | [EnumMember(Value = "Authenticated")]
35 | Authenticated,
36 | ///
37 | /// Authentication was not successful.
38 | ///
39 | [EnumMember(Value = "NotAuthenticated")]
40 | NotAuthenticated
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Less3/Classes/AuthorizationResult.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using System.Text.Json;
7 | using System.Text.Json.Serialization;
8 | using System.Runtime.Serialization;
9 |
10 | ///
11 | /// Authorization result.
12 | ///
13 | [JsonConverter(typeof(JsonStringEnumConverter))]
14 | public enum AuthorizationResult
15 | {
16 | ///
17 | /// Operation authorized due to admin privileges.
18 | ///
19 | [EnumMember(Value = "AdminAuthorized")]
20 | AdminAuthorized,
21 | ///
22 | /// Service operations are by default permitted for any authenticated user.
23 | ///
24 | [EnumMember(Value = "PermitService")]
25 | PermitService,
26 | ///
27 | /// Operation permitted due to bucket global configuration.
28 | ///
29 | [EnumMember(Value = "PermitBucketGlobalConfig")]
30 | PermitBucketGlobalConfig,
31 | ///
32 | /// Operation permitted due to bucket all users access control list.
33 | ///
34 | [EnumMember(Value = "PermitBucketAllUsersAcl")]
35 | PermitBucketAllUsersAcl,
36 | ///
37 | /// Operation permitted due to bucket authenticated users access control list.
38 | ///
39 | [EnumMember(Value = "PermitBucketAuthUserAcl")]
40 | PermitBucketAuthUserAcl,
41 | ///
42 | /// Operation permitted due to bucket user access control list.
43 | ///
44 | [EnumMember(Value = "PermitBucketUserAcl")]
45 | PermitBucketUserAcl,
46 | ///
47 | /// Operation permitted due to bucket ownership.
48 | ///
49 | [EnumMember(Value = "PermitBucketOwnership")]
50 | PermitBucketOwnership,
51 | ///
52 | /// Operation permitted due to object all users access control list.
53 | ///
54 | [EnumMember(Value = "PermitObjectAllUsersAcl")]
55 | PermitObjectAllUsersAcl,
56 | ///
57 | /// Operation permitted due to object authenticated users access control list.
58 | ///
59 | [EnumMember(Value = "PermitObjectAuthUserAcl")]
60 | PermitObjectAuthUserAcl,
61 | ///
62 | /// Operation permitted due to object user access control list.
63 | ///
64 | [EnumMember(Value = "PermitObjectUserAcl")]
65 | PermitObjectUserAcl,
66 | ///
67 | /// Operation permitted due to object ownership.
68 | ///
69 | [EnumMember(Value = "PermitObjectOwnership")]
70 | PermitObjectOwnership,
71 | ///
72 | /// Operation not authorized.
73 | ///
74 | [EnumMember(Value = "NotAuthorized")]
75 | NotAuthorized
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Less3/Classes/Bucket.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.IO;
7 | using System.Text;
8 | using System.Text.Json;
9 | using System.Text.Json.Serialization;
10 | using Watson.ORM.Core;
11 | using Less3.Storage;
12 |
13 | ///
14 | /// Bucket configuration.
15 | ///
16 | [Table("buckets")]
17 | public class Bucket
18 | {
19 | #region Public-Members
20 |
21 | ///
22 | /// Database identifier.
23 | ///
24 | [JsonIgnore]
25 | [Column("id", true, DataTypes.Int, false)]
26 | public int Id { get; set; } = 0;
27 |
28 | ///
29 | /// GUID of the bucket.
30 | ///
31 | [Column("guid", false, DataTypes.Nvarchar, 64, false)]
32 | public string GUID { get; set; } = Guid.NewGuid().ToString();
33 |
34 | ///
35 | /// GUID of the owner.
36 | ///
37 | [Column("ownerguid", false, DataTypes.Nvarchar, 64, false)]
38 | public string OwnerGUID { get; set; } = Guid.NewGuid().ToString();
39 |
40 | ///
41 | /// Name of the bucket.
42 | ///
43 | [Column("name", false, DataTypes.Nvarchar, 256, false)]
44 | public string Name { get; set; } = null;
45 |
46 | ///
47 | /// Bucket region string.
48 | ///
49 | [Column("regionstring", false, DataTypes.Nvarchar, 32, false)]
50 | public string RegionString { get; set; } = "us-west-1";
51 |
52 | ///
53 | /// Type of storage driver.
54 | ///
55 | [Column("storagetype", false, DataTypes.Enum, 16, false)]
56 | public StorageDriverType StorageType { get; set; } = StorageDriverType.Disk;
57 |
58 | ///
59 | /// Objects directory.
60 | ///
61 | [Column("diskdirectory", false, DataTypes.Nvarchar, 256, false)]
62 | public string DiskDirectory { get; set; } = "./disk/";
63 |
64 | ///
65 | /// Enable or disable versioning.
66 | ///
67 | [Column("enableversioning", false, DataTypes.Boolean, false)]
68 | public bool EnableVersioning { get; set; } = false;
69 |
70 | ///
71 | /// Enable or disable public write.
72 | ///
73 | [Column("enablepublicwrite", false, DataTypes.Boolean, false)]
74 | public bool EnablePublicWrite { get; set; } = false;
75 |
76 | ///
77 | /// Enable or disable public read.
78 | ///
79 | [Column("enablepublicread", false, DataTypes.Boolean, false)]
80 | public bool EnablePublicRead { get; set; } = false;
81 |
82 | ///
83 | /// Creation timestamp.
84 | ///
85 | [Column("createdutc", false, DataTypes.DateTime, false)]
86 | public DateTime CreatedUtc { get; set; } = DateTime.Now.ToUniversalTime();
87 |
88 | #endregion
89 |
90 | #region Private-Members
91 |
92 | #endregion
93 |
94 | #region Constructors-and-Factories
95 |
96 | ///
97 | /// Instantiate.
98 | ///
99 | public Bucket()
100 | {
101 |
102 | }
103 |
104 | ///
105 | /// Instantiate.
106 | ///
107 | /// Name.
108 | /// Owner GUID.
109 | /// Storage type.
110 | /// Disk directory.
111 | /// Region.
112 | public Bucket(
113 | string name,
114 | string owner,
115 | StorageDriverType storageType,
116 | string diskDirectory,
117 | string region = "us-west-1")
118 | {
119 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
120 | if (String.IsNullOrEmpty(owner)) throw new ArgumentNullException(nameof(owner));
121 | if (String.IsNullOrEmpty(diskDirectory)) throw new ArgumentNullException(nameof(diskDirectory));
122 |
123 | Name = name;
124 | RegionString = region;
125 | StorageType = storageType;
126 | DiskDirectory = diskDirectory;
127 | OwnerGUID = owner;
128 | CreatedUtc = DateTime.Now.ToUniversalTime();
129 | }
130 |
131 | ///
132 | /// Instantiate.
133 | ///
134 | /// GUID.
135 | /// Name.
136 | /// Owner GUID.
137 | /// Storage type.
138 | /// Disk directory.
139 | /// Region.
140 | public Bucket(
141 | string guid,
142 | string name,
143 | string owner,
144 | StorageDriverType storageType,
145 | string diskDirectory,
146 | string region = "us-west-1")
147 | {
148 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
149 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
150 | if (String.IsNullOrEmpty(owner)) throw new ArgumentNullException(nameof(owner));
151 | if (String.IsNullOrEmpty(diskDirectory)) throw new ArgumentNullException(nameof(diskDirectory));
152 |
153 | GUID = guid;
154 | Name = name;
155 | RegionString = region;
156 | StorageType = storageType;
157 | DiskDirectory = diskDirectory;
158 | OwnerGUID = owner;
159 | CreatedUtc = DateTime.Now.ToUniversalTime();
160 | }
161 |
162 | #endregion
163 |
164 | #region Public-Methods
165 |
166 | #endregion
167 |
168 | #region Private-Methods
169 |
170 | #endregion
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/Less3/Classes/BucketAcl.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using Watson.ORM.Core;
10 |
11 | ///
12 | /// Access control list entry for a bucket.
13 | ///
14 | [Table("bucketacls")]
15 | public class BucketAcl
16 | {
17 | #region Public-Members
18 |
19 | ///
20 | /// Database identifier.
21 | ///
22 | [JsonIgnore]
23 | [Column("id", true, DataTypes.Int, false)]
24 | public int Id { get; set; } = 0;
25 |
26 | ///
27 | /// GUID.
28 | ///
29 | [Column("guid", false, DataTypes.Nvarchar, 64, false)]
30 | public string GUID { get; set; } = Guid.NewGuid().ToString();
31 |
32 | ///
33 | /// User group.
34 | ///
35 | [Column("usergroup", false, DataTypes.Nvarchar, 256, true)]
36 | public string UserGroup { get; set; } = null;
37 |
38 | ///
39 | /// Bucket GUID.
40 | ///
41 | [Column("bucketguid", false, DataTypes.Nvarchar, 64, true)]
42 | public string BucketGUID { get; set; } = null;
43 |
44 | ///
45 | /// User GUID.
46 | ///
47 | [Column("userguid", false, DataTypes.Nvarchar, 64, true)]
48 | public string UserGUID { get; set; } = null;
49 |
50 | ///
51 | /// GUID of the issuing user.
52 | ///
53 | [Column("issuedbyuserguid", false, DataTypes.Nvarchar, 64, true)]
54 | public string IssuedByUserGUID { get; set; } = null;
55 |
56 | ///
57 | /// Permit read operations.
58 | ///
59 | [Column("permitread", false, DataTypes.Boolean, false)]
60 | public bool PermitRead { get; set; } = false;
61 |
62 | ///
63 | /// Permit write operations.
64 | ///
65 | [Column("permitwrite", false, DataTypes.Boolean, false)]
66 | public bool PermitWrite { get; set; } = false;
67 |
68 | ///
69 | /// Permit access control read operations.
70 | ///
71 | [Column("permitreadacp", false, DataTypes.Boolean, false)]
72 | public bool PermitReadAcp { get; set; } = false;
73 |
74 | ///
75 | /// Permit access control write operations.
76 | ///
77 | [Column("permitwriteacp", false, DataTypes.Boolean, false)]
78 | public bool PermitWriteAcp { get; set; } = false;
79 |
80 | ///
81 | /// Permit full control.
82 | ///
83 | [Column("permitfullcontrol", false, DataTypes.Boolean, false)]
84 | public bool FullControl { get; set; } = false;
85 |
86 | ///
87 | /// Timestamp from record creation, in UTC time.
88 | ///
89 | [Column("createdutc", false, DataTypes.DateTime, 6, 6, false)]
90 | public DateTime CreatedUtc { get; set; } = DateTime.Now.ToUniversalTime();
91 |
92 | #endregion
93 |
94 | #region Private-Members
95 |
96 | #endregion
97 |
98 | #region Constructors-and-Factories
99 |
100 | ///
101 | /// Instantiate.
102 | ///
103 | public BucketAcl()
104 | {
105 |
106 | }
107 |
108 | ///
109 | /// Create a group ACL.
110 | ///
111 | /// Group name.
112 | /// Issued by user GUID.
113 | /// Bucket GUID.
114 | /// Permit read.
115 | /// Permit write.
116 | /// Permit access control read.
117 | /// Permit access control write.
118 | /// Full control.
119 | /// Instance.
120 | public static BucketAcl GroupAcl(
121 | string groupName,
122 | string issuedByUserGuid,
123 | string bucketGuid,
124 | bool permitRead,
125 | bool permitWrite,
126 | bool permitReadAcp,
127 | bool permitWriteAcp,
128 | bool fullControl)
129 | {
130 | if (String.IsNullOrEmpty(groupName)) throw new ArgumentNullException(nameof(groupName));
131 | if (String.IsNullOrEmpty(issuedByUserGuid)) throw new ArgumentNullException(nameof(issuedByUserGuid));
132 | if (String.IsNullOrEmpty(bucketGuid)) throw new ArgumentNullException(nameof(bucketGuid));
133 |
134 | BucketAcl ret = new BucketAcl();
135 |
136 | ret.UserGroup = groupName;
137 | ret.UserGUID = null;
138 | ret.IssuedByUserGUID = issuedByUserGuid;
139 | ret.BucketGUID = bucketGuid;
140 |
141 | ret.PermitRead = permitRead;
142 | ret.PermitWrite = permitWrite;
143 | ret.PermitReadAcp = permitReadAcp;
144 | ret.PermitWriteAcp = permitWriteAcp;
145 | ret.FullControl = fullControl;
146 |
147 | return ret;
148 | }
149 |
150 | ///
151 | /// Create a user ACL.
152 | ///
153 | /// User GUID.
154 | /// Issued by user GUID.
155 | /// Bucket GUID.
156 | /// Permit read.
157 | /// Permit write.
158 | /// Permit access control read.
159 | /// Permit access control write.
160 | /// Full control.
161 | /// Instance.
162 | public static BucketAcl UserAcl(
163 | string userGuid,
164 | string issuedByUserGuid,
165 | string bucketGuid,
166 | bool permitRead,
167 | bool permitWrite,
168 | bool permitReadAcp,
169 | bool permitWriteAcp,
170 | bool fullControl)
171 | {
172 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
173 | if (String.IsNullOrEmpty(issuedByUserGuid)) throw new ArgumentNullException(nameof(issuedByUserGuid));
174 | if (String.IsNullOrEmpty(bucketGuid)) throw new ArgumentNullException(nameof(bucketGuid));
175 |
176 | BucketAcl ret = new BucketAcl();
177 |
178 | ret.UserGroup = null;
179 | ret.UserGUID = userGuid;
180 | ret.IssuedByUserGUID = issuedByUserGuid;
181 | ret.BucketGUID = bucketGuid;
182 |
183 | ret.PermitRead = permitRead;
184 | ret.PermitWrite = permitWrite;
185 | ret.PermitReadAcp = permitReadAcp;
186 | ret.PermitWriteAcp = permitWriteAcp;
187 | ret.FullControl = fullControl;
188 |
189 | return ret;
190 | }
191 |
192 | #endregion
193 |
194 | #region Public-Methods
195 |
196 | ///
197 | /// Human-readable string of the object.
198 | ///
199 | ///
200 | public override string ToString()
201 | {
202 | string
203 | ret = "--- Bucket ACL " + Id + " ---" + Environment.NewLine +
204 | " User group : " + UserGroup + Environment.NewLine +
205 | " User GUID : " + UserGUID + Environment.NewLine +
206 | " Issued by : " + IssuedByUserGUID + Environment.NewLine +
207 | " Permissions : " + Environment.NewLine +
208 | " READ : " + PermitRead.ToString() + Environment.NewLine +
209 | " WRITE : " + PermitWrite.ToString() + Environment.NewLine +
210 | " READ_ACP : " + PermitReadAcp.ToString() + Environment.NewLine +
211 | " WRITE_ACP : " + PermitWriteAcp.ToString() + Environment.NewLine +
212 | " FULL_CONTROL : " + FullControl.ToString() + Environment.NewLine;
213 |
214 | return ret;
215 | }
216 |
217 | #endregion
218 |
219 | #region Private-Methods
220 |
221 | #endregion
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/Less3/Classes/BucketManager.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 |
11 | using SyslogLogging;
12 | using Watson.ORM;
13 |
14 | ///
15 | /// Bucket manager.
16 | ///
17 | public class BucketManager
18 | {
19 | #region Public-Members
20 |
21 | #endregion
22 |
23 | #region Private-Members
24 |
25 | private Settings _Settings;
26 | private LoggingModule _Logging;
27 | private ConfigManager _Config;
28 | private WatsonORM _ORM;
29 |
30 | private readonly object _BucketsLock = new object();
31 | private List _Buckets = new List();
32 |
33 | #endregion
34 |
35 | #region Constructors-and-Factories
36 |
37 | internal BucketManager(Settings settings, LoggingModule logging, ConfigManager config, WatsonORM orm)
38 | {
39 | _Settings = settings ?? throw new ArgumentNullException(nameof(settings));
40 | _Logging = logging ?? throw new ArgumentNullException(nameof(logging));
41 | _Config = config ?? throw new ArgumentNullException(nameof(config));
42 | _ORM = orm ?? throw new ArgumentNullException(nameof(orm));
43 |
44 | InitializeBuckets();
45 | }
46 |
47 | #endregion
48 |
49 | #region Internal-Methods
50 |
51 | internal bool Add(Bucket bucket)
52 | {
53 | if (bucket == null) throw new ArgumentNullException(nameof(bucket));
54 |
55 | bool success = _Config.AddBucket(bucket);
56 | if (success)
57 | {
58 | BucketClient client = new BucketClient(_Settings, _Logging, bucket, _ORM);
59 |
60 | lock (_BucketsLock)
61 | {
62 | _Buckets.Add(client);
63 | }
64 |
65 | InitializeBucket(bucket);
66 | }
67 |
68 | return success;
69 | }
70 |
71 | internal bool Remove(Bucket bucket, bool destroy)
72 | {
73 | if (bucket == null) throw new ArgumentNullException(nameof(bucket));
74 |
75 | bool removed = false;
76 |
77 | if (_Config.BucketExists(bucket.Name))
78 | {
79 | BucketClient client = GetClient(bucket.Name);
80 | if (client != null)
81 | {
82 | lock (_BucketsLock)
83 | {
84 | List clients = _Buckets.Where(b => !b.Name.Equals(bucket.Name)).ToList();
85 | _Buckets = new List(clients);
86 | client.Dispose();
87 | client = null;
88 | }
89 | }
90 |
91 | removed = true;
92 |
93 | _Config.DeleteBucket(bucket.GUID);
94 | }
95 |
96 | if (removed)
97 | {
98 | if (destroy)
99 | {
100 | if (!Destroy(bucket))
101 | _Logging.Warn("BucketManager Remove issues encountered removing data for bucket " + bucket.Name + ", cleanup required");
102 | else
103 | _Logging.Info("BucketManager Remove removed bucket with name " + bucket.Name + " with owner " + bucket.OwnerGUID);
104 | }
105 | else
106 | {
107 | _Logging.Info("BucketManager Remove removed bucket with name " + bucket.Name + " with owner " + bucket.OwnerGUID);
108 | }
109 |
110 | return true;
111 | }
112 | else
113 | {
114 | _Logging.Warn("BucketManager Remove bucket with name " + bucket.Name + " not found");
115 | return false;
116 | }
117 | }
118 |
119 | internal Bucket GetByName(string bucketName)
120 | {
121 | if (String.IsNullOrEmpty(bucketName)) throw new ArgumentNullException(nameof(bucketName));
122 | return _Config.GetBucketByName(bucketName);
123 | }
124 |
125 | internal Bucket GetByGuid(string guid)
126 | {
127 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
128 | return _Config.GetBucketByGuid(guid);
129 | }
130 |
131 | internal BucketClient GetClient(string bucketName)
132 | {
133 | if (String.IsNullOrEmpty(bucketName)) throw new ArgumentNullException(nameof(bucketName));
134 |
135 | lock (_BucketsLock)
136 | {
137 | bool exists = _Buckets.Exists(b => b.Name.Equals(bucketName));
138 | if (!exists) return null;
139 | return _Buckets.First(b => b.Name.Equals(bucketName));
140 | }
141 | }
142 |
143 | internal List GetUserBuckets(string userGuid)
144 | {
145 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
146 | return _Config.GetBucketsByUser(userGuid);
147 | }
148 |
149 | #endregion
150 |
151 | #region Private-Methods
152 |
153 | private void InitializeBuckets()
154 | {
155 | List buckets = _Config.GetBuckets();
156 |
157 | if (buckets != null && buckets.Count > 0)
158 | {
159 | foreach (Bucket curr in buckets)
160 | {
161 | InitializeBucket(curr);
162 | }
163 | }
164 | }
165 |
166 | private void InitializeBucket(Bucket bucket)
167 | {
168 | lock (_BucketsLock)
169 | {
170 | BucketClient client = new BucketClient(_Settings, _Logging, bucket, _ORM);
171 | _Buckets.Add(client);
172 | }
173 | }
174 |
175 | private bool Destroy(Bucket bucket)
176 | {
177 | #region Delete-Object-Files
178 |
179 | bool objectFilesDelete = false;
180 | try
181 | {
182 | if (Directory.Exists(bucket.DiskDirectory))
183 | ClearDirectory(bucket.DiskDirectory);
184 | objectFilesDelete = true;
185 | }
186 | catch (Exception)
187 | {
188 |
189 | }
190 |
191 | #endregion
192 |
193 | #region Remove-Objects-Directory
194 |
195 | bool objectsDirectoryDelete = false;
196 | try
197 | {
198 | if (Directory.Exists(bucket.DiskDirectory))
199 | Directory.Delete(bucket.DiskDirectory);
200 | objectsDirectoryDelete = true;
201 | }
202 | catch (Exception)
203 | {
204 |
205 | }
206 |
207 | #endregion
208 |
209 | #region Delete-Root-Files
210 |
211 | bool rootFilesDelete = false;
212 |
213 | try
214 | {
215 | if (Directory.Exists(_Settings.Storage.DiskDirectory + bucket.Name))
216 | {
217 | ClearDirectory(_Settings.Storage.DiskDirectory + bucket.Name);
218 | rootFilesDelete = true;
219 | }
220 | }
221 | catch (Exception)
222 | {
223 |
224 | }
225 |
226 | #endregion
227 |
228 | #region Remove-Root-Directory
229 |
230 | bool rootDirectoryDelete = false;
231 |
232 | try
233 | {
234 | if (Directory.Exists(_Settings.Storage.DiskDirectory + bucket.Name))
235 | {
236 | Directory.Delete(_Settings.Storage.DiskDirectory + bucket.Name);
237 | rootDirectoryDelete = true;
238 | }
239 | }
240 | catch (Exception)
241 | {
242 |
243 | }
244 |
245 | #endregion
246 |
247 | _Logging.Info("Destroy bucket " + bucket.Name + ": " +
248 | "obj files [" + objectFilesDelete + "] " +
249 | "obj dir [" + objectsDirectoryDelete + "] " +
250 | "root files [" + rootFilesDelete + "] " +
251 | "root dir [" + rootDirectoryDelete + "]");
252 |
253 | return objectFilesDelete && objectsDirectoryDelete && rootFilesDelete && rootDirectoryDelete;
254 | }
255 |
256 | private void ClearDirectory(string path)
257 | {
258 | DirectoryInfo dir = new DirectoryInfo(path);
259 | foreach (FileInfo fi in dir.EnumerateFiles())
260 | {
261 | fi.Delete();
262 | }
263 | }
264 |
265 | #endregion
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/src/Less3/Classes/BucketStatistics.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | ///
8 | /// Bucket statistics.
9 | ///
10 | public class BucketStatistics
11 | {
12 | #region Public-Members
13 |
14 | ///
15 | /// The name of the bucket.
16 | ///
17 | public string Name { get; set; } = null;
18 |
19 | ///
20 | /// GUID of the bucket.
21 | ///
22 | public string GUID { get; set; } = Guid.NewGuid().ToString();
23 |
24 | ///
25 | /// The number of objects in the bucket including all versions.
26 | ///
27 | public long Objects = 0;
28 |
29 | ///
30 | /// The number of bytes for all objects in the bucket.
31 | ///
32 | public long Bytes = 0;
33 |
34 | #endregion
35 |
36 | #region Private-Members
37 |
38 | #endregion
39 |
40 | #region Constructors-and-Factories
41 |
42 | ///
43 | /// Instantiate.
44 | ///
45 | public BucketStatistics()
46 | {
47 |
48 | }
49 |
50 | ///
51 | /// Instantiate.
52 | ///
53 | /// Name.
54 | /// GUID.
55 | /// Number of objects.
56 | /// Number of bytes.
57 | public BucketStatistics(string name, string guid, long objects, long bytes)
58 | {
59 |
60 | }
61 |
62 | #endregion
63 |
64 | #region Public-Methods
65 |
66 | #endregion
67 |
68 | #region Private-Methods
69 |
70 | #endregion
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Less3/Classes/BucketTag.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using Watson.ORM.Core;
10 |
11 | ///
12 | /// Tag entry for a bucket.
13 | ///
14 | [Table("buckettags")]
15 | public class BucketTag
16 | {
17 | #region Public-Members
18 |
19 | ///
20 | /// Database identifier.
21 | ///
22 | [JsonIgnore]
23 | [Column("id", true, DataTypes.Int, false)]
24 | public int Id { get; set; } = 0;
25 |
26 | ///
27 | /// GUID.
28 | ///
29 | [Column("guid", false, DataTypes.Nvarchar, 64, false)]
30 | public string GUID { get; set; } = Guid.NewGuid().ToString();
31 |
32 | ///
33 | /// GUID of the bucket.
34 | ///
35 | [Column("bucketguid", false, DataTypes.Nvarchar, 64, false)]
36 | public string BucketGUID { get; set; } = null;
37 |
38 | ///
39 | /// Key.
40 | ///
41 | [Column("tagkey", false, DataTypes.Nvarchar, 256, false)]
42 | public string Key { get; set; } = null;
43 |
44 | ///
45 | /// Value.
46 | ///
47 | [Column("tagvalue", false, DataTypes.Nvarchar, 1024, true)]
48 | public string Value { get; set; } = null;
49 |
50 | ///
51 | /// Timestamp from record creation, in UTC time.
52 | ///
53 | [Column("createdutc", false, DataTypes.DateTime, 6, 6, false)]
54 | public DateTime CreatedUtc { get; set; } = DateTime.Now.ToUniversalTime();
55 |
56 | #endregion
57 |
58 | #region Private-Members
59 |
60 | #endregion
61 |
62 | #region Constructors-and-Factories
63 |
64 | ///
65 | /// Instantiate.
66 | ///
67 | public BucketTag()
68 | {
69 |
70 | }
71 |
72 | ///
73 | /// Instantiate.
74 | ///
75 | /// Bucket GUID.
76 | /// Key.
77 | /// Value.
78 | public BucketTag(string bucketGuid, string key, string val)
79 | {
80 | if (String.IsNullOrEmpty(bucketGuid)) throw new ArgumentNullException(nameof(bucketGuid));
81 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
82 |
83 | BucketGUID = bucketGuid;
84 | Key = key;
85 | Value = val;
86 | }
87 |
88 | ///
89 | /// Instantiate.
90 | ///
91 | /// GUID.
92 | /// Bucket GUID.
93 | /// Key.
94 | /// Value.
95 | public BucketTag(string guid, string bucketGuid, string key, string val)
96 | {
97 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
98 | if (String.IsNullOrEmpty(bucketGuid)) throw new ArgumentNullException(nameof(bucketGuid));
99 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
100 |
101 | GUID = guid;
102 | BucketGUID = bucketGuid;
103 | Key = key;
104 | Value = val;
105 | }
106 |
107 | #endregion
108 |
109 | #region Public-Methods
110 |
111 | #endregion
112 |
113 | #region Private-Methods
114 |
115 | #endregion
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/Less3/Classes/ConfigManager.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Text;
7 |
8 | using DatabaseWrapper.Core;
9 | using ExpressionTree;
10 | using SyslogLogging;
11 | using Watson.ORM;
12 | using Watson.ORM.Core;
13 |
14 | ///
15 | /// Configuration manager.
16 | ///
17 | internal class ConfigManager
18 | {
19 | #region Public-Members
20 |
21 | #endregion
22 |
23 | #region Private-Members
24 |
25 | private Settings _Settings = null;
26 | private LoggingModule _Logging = null;
27 | private WatsonORM _ORM = null;
28 |
29 | #endregion
30 |
31 | #region Constructors-and-Factories
32 |
33 | internal ConfigManager(Settings settings, LoggingModule logging, WatsonORM orm)
34 | {
35 | _Settings = settings ?? throw new ArgumentNullException(nameof(settings));
36 | _Logging = logging ?? throw new ArgumentNullException(nameof(logging));
37 | _ORM = orm ?? throw new ArgumentNullException(nameof(orm));
38 | }
39 |
40 | #endregion
41 |
42 | #region Public-Methods
43 |
44 | #endregion
45 |
46 | #region Internal-User-Methods
47 |
48 | internal List GetUsers()
49 | {
50 | Expr e = new Expr(
51 | _ORM.GetColumnName(nameof(User.Id)),
52 | OperatorEnum.GreaterThan,
53 | 0);
54 | return _ORM.SelectMany(e);
55 | }
56 |
57 | internal bool UserGuidExists(string guid)
58 | {
59 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
60 |
61 | Expr e = new Expr(
62 | _ORM.GetColumnName(nameof(User.GUID)),
63 | OperatorEnum.Equals,
64 | guid);
65 |
66 | User user = _ORM.SelectFirst(e);
67 | if (user != null) return true;
68 | return false;
69 | }
70 |
71 | internal bool UserEmailExists(string email)
72 | {
73 | if (String.IsNullOrEmpty(email)) throw new ArgumentNullException(nameof(email));
74 |
75 | Expr e = new Expr(
76 | _ORM.GetColumnName(nameof(User.Email)),
77 | OperatorEnum.Equals,
78 | email);
79 |
80 | User user = _ORM.SelectFirst(e);
81 | if (user != null) return true;
82 | return false;
83 | }
84 |
85 | internal User GetUserByGuid(string guid)
86 | {
87 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
88 |
89 | Expr e = new Expr(
90 | _ORM.GetColumnName(nameof(User.GUID)),
91 | OperatorEnum.Equals,
92 | guid);
93 |
94 | return _ORM.SelectFirst(e);
95 | }
96 |
97 | internal User GetUserByName(string name)
98 | {
99 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
100 |
101 | Expr e = new Expr(
102 | _ORM.GetColumnName(nameof(User.Name)),
103 | OperatorEnum.Equals,
104 | name);
105 |
106 | return _ORM.SelectFirst(e);
107 | }
108 |
109 | internal User GetUserByEmail(string email)
110 | {
111 | if (String.IsNullOrEmpty(email)) throw new ArgumentNullException(nameof(email));
112 |
113 | Expr e = new Expr(
114 | _ORM.GetColumnName(nameof(User.Email)),
115 | OperatorEnum.Equals,
116 | email);
117 |
118 | return _ORM.SelectFirst(e);
119 | }
120 |
121 | internal User GetUserByAccessKey(string accessKey)
122 | {
123 | if (String.IsNullOrEmpty(accessKey)) throw new ArgumentNullException(nameof(accessKey));
124 |
125 | Credential cred = GetCredentialByAccessKey(accessKey);
126 | if (cred == null)
127 | {
128 | _Logging.Warn("ConfigManager GetUserByAccessKey access key " + accessKey + " not found");
129 | return null;
130 | }
131 |
132 | User user = GetUserByGuid(cred.UserGUID);
133 | if (user == null)
134 | {
135 | _Logging.Warn("ConfigManager GetUserByAccessKey user GUID " + cred.UserGUID + " not found, referenced by credential GUID " + cred.GUID);
136 | return null;
137 | }
138 |
139 | return user;
140 | }
141 |
142 | internal bool AddUser(string guid, string name, string email)
143 | {
144 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
145 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
146 | if (String.IsNullOrEmpty(email)) throw new ArgumentNullException(nameof(email));
147 |
148 | User user = new User(guid, name, email);
149 | return AddUser(user);
150 | }
151 |
152 | internal bool AddUser(User user)
153 | {
154 | if (user == null) throw new ArgumentNullException(nameof(user));
155 |
156 | User userByGuid = GetUserByGuid(user.GUID);
157 | if (userByGuid != null)
158 | {
159 | _Logging.Warn("ConfigManager AddUser user GUID " + user.GUID + " already exists");
160 | return false;
161 | }
162 |
163 | User userByEmail = GetUserByEmail(user.Email);
164 | if (userByEmail != null)
165 | {
166 | _Logging.Warn("ConfigManager AddUser user email " + user.Email + " already exists");
167 | return false;
168 | }
169 |
170 | _ORM.Insert(user);
171 | return true;
172 | }
173 |
174 | internal void DeleteUser(string guid)
175 | {
176 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
177 | User tempUser = GetUserByGuid(guid);
178 | if (tempUser != null)
179 | {
180 | _ORM.Delete(tempUser);
181 | }
182 | }
183 |
184 | #endregion
185 |
186 | #region Internal-Credential-Methods
187 |
188 | internal List GetCredentials()
189 | {
190 | Expr e = new Expr(
191 | _ORM.GetColumnName(nameof(Credential.Id)),
192 | OperatorEnum.GreaterThan,
193 | 0);
194 | return _ORM.SelectMany(e);
195 | }
196 |
197 | internal bool CredentialGuidExists(string guid)
198 | {
199 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
200 |
201 | Expr e = new Expr(
202 | _ORM.GetColumnName(nameof(Credential.GUID)),
203 | OperatorEnum.Equals,
204 | guid);
205 |
206 | Credential cred = _ORM.SelectFirst(e);
207 | if (cred != null) return true;
208 | return false;
209 | }
210 |
211 | internal Credential GetCredentialByGuid(string guid)
212 | {
213 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
214 |
215 | Expr e = new Expr(
216 | _ORM.GetColumnName(nameof(Credential.GUID)),
217 | OperatorEnum.Equals,
218 | guid);
219 |
220 | return _ORM.SelectFirst(e);
221 | }
222 |
223 | internal List GetCredentialsByUser(string userGuid)
224 | {
225 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
226 |
227 | Expr e = new Expr(
228 | _ORM.GetColumnName(nameof(Credential.UserGUID)),
229 | OperatorEnum.Equals,
230 | userGuid);
231 |
232 | return _ORM.SelectMany(e);
233 | }
234 |
235 | internal Credential GetCredentialByAccessKey(string accessKey)
236 | {
237 | if (String.IsNullOrEmpty(accessKey)) throw new ArgumentNullException(nameof(accessKey));
238 |
239 | Expr e = new Expr(
240 | _ORM.GetColumnName(nameof(Credential.AccessKey)),
241 | OperatorEnum.Equals,
242 | accessKey);
243 |
244 | return _ORM.SelectFirst(e);
245 | }
246 |
247 | internal bool AddCredential(string userGuid, string description, string accessKey, string secretKey, bool isBase64)
248 | {
249 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
250 | if (String.IsNullOrEmpty(accessKey)) throw new ArgumentNullException(nameof(accessKey));
251 | if (String.IsNullOrEmpty(secretKey)) throw new ArgumentNullException(nameof(secretKey));
252 |
253 | Credential cred = new Credential(userGuid, description, accessKey, secretKey, isBase64);
254 | return AddCredential(cred);
255 | }
256 |
257 | internal bool AddCredential(Credential cred)
258 | {
259 | if (cred == null) throw new ArgumentNullException(nameof(cred));
260 |
261 | Credential credByGuid = GetCredentialByGuid(cred.GUID);
262 | if (credByGuid != null)
263 | {
264 | _Logging.Warn("ConfigManager AddCredential credential GUID " + cred.GUID + " already exists");
265 | return false;
266 | }
267 |
268 | Credential credByKey = GetCredentialByAccessKey(cred.AccessKey);
269 | if (credByKey != null)
270 | {
271 | _Logging.Warn("ConfigManager AddCredential access key " + cred.AccessKey + " already exists");
272 | return false;
273 | }
274 |
275 | _ORM.Insert(cred);
276 | return true;
277 | }
278 |
279 | internal void DeleteCredential(string guid)
280 | {
281 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
282 | Credential tempCred = GetCredentialByGuid(guid);
283 | if (tempCred != null)
284 | {
285 | _ORM.Delete(tempCred);
286 | }
287 | }
288 |
289 | #endregion
290 |
291 | #region Internal-Bucket-Methods
292 |
293 | internal List GetBuckets()
294 | {
295 | Expr e = new Expr(
296 | _ORM.GetColumnName(nameof(Bucket.Id)),
297 | OperatorEnum.GreaterThan,
298 | 0);
299 |
300 | return _ORM.SelectMany(e);
301 | }
302 |
303 | internal bool BucketExists(string name)
304 | {
305 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
306 |
307 | Expr e = new Expr(
308 | _ORM.GetColumnName(nameof(Bucket.Name)),
309 | OperatorEnum.Equals,
310 | name);
311 |
312 | Bucket bucket = _ORM.SelectFirst(e);
313 | if (bucket != null) return true;
314 | return false;
315 | }
316 |
317 | internal List GetBucketsByUser(string userGuid)
318 | {
319 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
320 |
321 | Expr e = new Expr(
322 | _ORM.GetColumnName(nameof(Bucket.OwnerGUID)),
323 | OperatorEnum.Equals,
324 | userGuid);
325 |
326 | return _ORM.SelectMany(e);
327 | }
328 |
329 | internal Bucket GetBucketByGuid(string guid)
330 | {
331 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
332 |
333 | Expr e = new Expr(
334 | _ORM.GetColumnName(nameof(Bucket.GUID)),
335 | OperatorEnum.Equals,
336 | guid);
337 |
338 | return _ORM.SelectFirst(e);
339 | }
340 |
341 | internal Bucket GetBucketByName(string name)
342 | {
343 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
344 |
345 | Expr e = new Expr(
346 | _ORM.GetColumnName(nameof(Bucket.Name)),
347 | OperatorEnum.Equals,
348 | name);
349 |
350 | return _ORM.SelectFirst(e);
351 | }
352 |
353 | internal bool AddBucket(string userGuid, string name)
354 | {
355 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
356 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
357 |
358 | Bucket bucket = new Bucket(
359 | Guid.NewGuid().ToString(),
360 | name,
361 | userGuid,
362 | _Settings.Storage.StorageType,
363 | _Settings.Storage.DiskDirectory + name + "/Objects");
364 |
365 | return AddBucket(bucket);
366 | }
367 |
368 | internal bool AddBucket(Bucket bucket)
369 | {
370 | if (bucket == null) throw new ArgumentNullException(nameof(bucket));
371 |
372 | if (BucketExists(bucket.Name))
373 | {
374 | _Logging.Warn("ConfigManager AddBucket bucket " + bucket.Name + " already exists");
375 | return false;
376 | }
377 |
378 | _ORM.Insert(bucket);
379 | return true;
380 | }
381 |
382 | internal void DeleteBucket(string guid)
383 | {
384 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
385 | Bucket bucket = GetBucketByGuid(guid);
386 | if (bucket != null)
387 | {
388 | _ORM.Delete(bucket);
389 | }
390 | }
391 |
392 | #endregion
393 | }
394 | }
395 |
--------------------------------------------------------------------------------
/src/Less3/Classes/ConsoleManager.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using SyslogLogging;
11 |
12 | ///
13 | /// Console for less3.
14 | ///
15 | internal class ConsoleManager
16 | {
17 | #region Public-Members
18 |
19 | #endregion
20 |
21 | #region Private-Members
22 |
23 | private bool _Enabled { get; set; }
24 | private Settings _Settings { get; set; }
25 | private LoggingModule _Logging { get; set; }
26 |
27 | #endregion
28 |
29 | #region Constructors-and-Factories
30 |
31 | internal ConsoleManager(
32 | Settings settings,
33 | LoggingModule logging)
34 | {
35 | if (settings == null) throw new ArgumentNullException(nameof(settings));
36 | if (logging == null) throw new ArgumentNullException(nameof(logging));
37 |
38 | _Enabled = true;
39 |
40 | _Settings = settings;
41 | _Logging = logging;
42 | }
43 |
44 | #endregion
45 |
46 | #region Public-Methods
47 |
48 | internal void Worker()
49 | {
50 | string userInput = "";
51 |
52 | while (_Enabled)
53 | {
54 | Console.Write("Command (? for help) > ");
55 | userInput = Console.ReadLine();
56 |
57 | if (userInput == null) continue;
58 | switch (userInput.ToLower().Trim())
59 | {
60 | case "?":
61 | Menu();
62 | break;
63 |
64 | case "c":
65 | case "cls":
66 | case "clear":
67 | Console.Clear();
68 | break;
69 |
70 | case "q":
71 | case "quit":
72 | _Enabled = false;
73 | break;
74 | }
75 | }
76 | }
77 |
78 | #endregion
79 |
80 | #region Private-Methods
81 |
82 | private void Menu()
83 | {
84 | Console.WriteLine(Common.Line(79, "-"));
85 | Console.WriteLine(" ? help / this menu");
86 | Console.WriteLine(" cls / c clear the console");
87 | Console.WriteLine(" quit / q exit the application");
88 | Console.WriteLine("");
89 | return;
90 | }
91 |
92 | #endregion
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/Less3/Classes/Constants.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 |
7 | internal static class Constants
8 | {
9 | // http://loveascii.com/hearts.html
10 | // http://patorjk.com/software/taag/#p=display&f=Small&t=less3
11 | internal static string Logo =
12 | @" ,d88b.d88b, _ ____ " + Environment.NewLine +
13 | @" 88888888888 | |___ _____|__ / " + Environment.NewLine +
14 | @" `Y8888888Y' | / -_|_-<_-<|_ \ " + Environment.NewLine +
15 | @" `Y888Y' |_\___/__/__/___/ " + Environment.NewLine +
16 | @" `Y' " + Environment.NewLine;
17 |
18 | internal static class Headers
19 | {
20 | internal static string RequestType = "X-Request-Type";
21 | internal static string AuthenticationResult = "X-Authentication-Result";
22 | internal static string AuthorizedBy = "X-Authorized-By";
23 |
24 | internal static string DeleteMarker = "X-Amz-Delete-Marker";
25 | internal static string AccessControlList = "X-Amz-Acl";
26 | internal static string AclGrantRead = "X-Amz-Grant-Read";
27 | internal static string AclGrantWrite = "X-Amz-Grant-Write";
28 | internal static string AclGrantReadAcp = "X-Amz-Grant-Read-Acp";
29 | internal static string AclGrantWriteAcp = "X-Amz-Grant-Write-Acp";
30 | internal static string AclGrantFullControl = "X-Amz-Grant-Full-Control";
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Less3/Classes/Credential.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using Watson.ORM.Core;
10 |
11 | ///
12 | /// Credential.
13 | ///
14 | [Table("credential")]
15 | public class Credential
16 | {
17 | #region Public-Members
18 |
19 | ///
20 | /// Database identifier.
21 | ///
22 | [JsonIgnore]
23 | [Column("id", true, DataTypes.Int, false)]
24 | public int Id { get; set; } = 0;
25 |
26 | ///
27 | /// GUID of the credential.
28 | ///
29 | [Column("guid", false, DataTypes.Nvarchar, 64, false)]
30 | public string GUID { get; set; } = Guid.NewGuid().ToString();
31 |
32 | ///
33 | /// User GUID.
34 | ///
35 | [Column("userguid", false, DataTypes.Nvarchar, 64, false)]
36 | public string UserGUID { get; set; } = Guid.NewGuid().ToString();
37 |
38 | ///
39 | /// Description.
40 | ///
41 | [Column("description", false, DataTypes.Nvarchar, 256, true)]
42 | public string Description { get; set; } = null;
43 |
44 | ///
45 | /// Access key.
46 | ///
47 | [Column("accesskey", false, DataTypes.Nvarchar, 256, false)]
48 | public string AccessKey { get; set; } = null;
49 |
50 | ///
51 | /// Secret key.
52 | ///
53 | [Column("secretkey", false, DataTypes.Nvarchar, 256, false)]
54 | public string SecretKey { get; set; } = null;
55 |
56 | ///
57 | /// Indicates if the secret key is base64 encoded.
58 | ///
59 | [Column("isbase64", false, DataTypes.Boolean, false)]
60 | public bool IsBase64 { get; set; } = false;
61 |
62 | ///
63 | /// Timestamp from record creation, in UTC time.
64 | ///
65 | [Column("createdutc", false, DataTypes.DateTime, 6, 6, false)]
66 | public DateTime CreatedUtc { get; set; } = DateTime.Now.ToUniversalTime();
67 |
68 | #endregion
69 |
70 | #region Private-Members
71 |
72 | #endregion
73 |
74 | #region Constructors-and-Factories
75 |
76 | ///
77 | /// Instantiate.
78 | ///
79 | public Credential()
80 | {
81 |
82 | }
83 |
84 | ///
85 | /// Instantiate.
86 | ///
87 | /// User GUID.
88 | /// Description.
89 | /// Access key.
90 | /// Secret key.
91 | /// Is base64 encoded.
92 | public Credential(string userGuid, string description, string accessKey, string secretKey, bool isBase64)
93 | {
94 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
95 | if (String.IsNullOrEmpty(accessKey)) throw new ArgumentNullException(nameof(accessKey));
96 | if (String.IsNullOrEmpty(secretKey)) throw new ArgumentNullException(nameof(secretKey));
97 |
98 | GUID = Guid.NewGuid().ToString();
99 | UserGUID = userGuid;
100 | Description = description;
101 | AccessKey = accessKey;
102 | SecretKey = secretKey;
103 | IsBase64 = isBase64;
104 | }
105 |
106 | ///
107 | /// Instantiate.
108 | ///
109 | /// GUID.
110 | /// User GUID.
111 | /// Description.
112 | /// Access key.
113 | /// Secret key.
114 | /// Is base64 encoded.
115 | public Credential(string guid, string userGuid, string description, string accessKey, string secretKey, bool isBase64)
116 | {
117 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
118 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
119 | if (String.IsNullOrEmpty(accessKey)) throw new ArgumentNullException(nameof(accessKey));
120 | if (String.IsNullOrEmpty(secretKey)) throw new ArgumentNullException(nameof(secretKey));
121 |
122 | GUID = guid;
123 | UserGUID = userGuid;
124 | Description = description;
125 | AccessKey = accessKey;
126 | SecretKey = secretKey;
127 | IsBase64 = isBase64;
128 | }
129 |
130 | #endregion
131 |
132 | #region Public-Methods
133 |
134 | #endregion
135 |
136 | #region Private-Methods
137 |
138 | #endregion
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/src/Less3/Classes/Obj.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using Watson.ORM.Core;
10 |
11 | ///
12 | /// Object stored in Less3.
13 | ///
14 | [Table("objects")]
15 | public class Obj
16 | {
17 | #region Public-Members
18 |
19 | ///
20 | /// Database identifier.
21 | ///
22 | [JsonIgnore]
23 | [Column("id", true, DataTypes.Int, false)]
24 | public int Id { get; set; } = 0;
25 |
26 | ///
27 | /// GUID of the object.
28 | ///
29 | [Column("guid", false, DataTypes.Nvarchar, 64, false)]
30 | public string GUID { get; set; } = Guid.NewGuid().ToString();
31 |
32 | ///
33 | /// GUID of the bucket.
34 | ///
35 | [Column("bucketguid", false, DataTypes.Nvarchar, 64, false)]
36 | public string BucketGUID { get; set; } = null;
37 |
38 | ///
39 | /// GUID of the owner.
40 | ///
41 | [Column("ownerguid", false, DataTypes.Nvarchar, 64, false)]
42 | public string OwnerGUID { get; set; } = null;
43 |
44 | ///
45 | /// GUID of the author.
46 | ///
47 | [Column("authorguid", false, DataTypes.Nvarchar, 64, false)]
48 | public string AuthorGUID { get; set; } = null;
49 |
50 | ///
51 | /// Object key.
52 | ///
53 | [Column("key", false, DataTypes.Nvarchar, 256, false)]
54 | public string Key { get; set; } = null;
55 |
56 | ///
57 | /// Content type.
58 | ///
59 | [Column("contenttype", false, DataTypes.Nvarchar, 128, true)]
60 | public string ContentType { get; set; } = "application/octet-stream";
61 |
62 | ///
63 | /// Content length.
64 | ///
65 | [Column("contentlength", false, DataTypes.Long, false)]
66 | public long ContentLength { get; set; } = 0;
67 |
68 | ///
69 | /// Object version.
70 | ///
71 | [Column("version", false, DataTypes.Long, false)]
72 | public long Version { get; set; } = 1;
73 |
74 | ///
75 | /// ETag of the object.
76 | ///
77 | [Column("etag", false, DataTypes.Nvarchar, 64, true)]
78 | public string Etag { get; set; } = null;
79 |
80 | ///
81 | /// Retention type.
82 | ///
83 | [Column("retention", false, DataTypes.Enum, 32, true)]
84 | public RetentionType Retention { get; set; } = RetentionType.NONE;
85 |
86 | ///
87 | /// BLOB filename.
88 | ///
89 | [Column("blobfilename", false, DataTypes.Nvarchar, 256, false)]
90 | public string BlobFilename { get; set; } = null;
91 |
92 | ///
93 | /// Indicates if the object is a folder, i.e. ends with '/' and has a content length of 0.
94 | ///
95 | [Column("isfolder", false, DataTypes.Boolean, false)]
96 | public bool IsFolder { get; set; } = false;
97 |
98 | ///
99 | /// Delete marker.
100 | ///
101 | [Column("deletemarker", false, DataTypes.Boolean, false)]
102 | public bool DeleteMarker { get; set; } = false;
103 |
104 | ///
105 | /// MD5.
106 | ///
107 | [Column("md5", false, DataTypes.Nvarchar, 32, true)]
108 | public string Md5 { get; set; } = null;
109 |
110 | ///
111 | /// Creation timestamp.
112 | ///
113 | [Column("createdutc", false, DataTypes.DateTime, false)]
114 | public DateTime CreatedUtc { get; set; } = DateTime.Now.ToUniversalTime();
115 |
116 | ///
117 | /// Last update timestamp.
118 | ///
119 | [Column("lastupdateutc", false, DataTypes.DateTime, false)]
120 | public DateTime LastUpdateUtc { get; set; } = DateTime.Now.ToUniversalTime();
121 |
122 | ///
123 | /// Last access timestamp.
124 | ///
125 | [Column("lastaccessutc", false, DataTypes.DateTime, false)]
126 | public DateTime LastAccessUtc { get; set; } = DateTime.Now.ToUniversalTime();
127 |
128 | ///
129 | /// Object expiration timestamp.
130 | ///
131 | [Column("expirationutc", false, DataTypes.DateTime, true)]
132 | public DateTime? ExpirationUtc = null;
133 |
134 | #endregion
135 |
136 | #region Private-Members
137 |
138 | #endregion
139 |
140 | #region Constructors-and-Factories
141 |
142 | ///
143 | /// Instantiate.
144 | ///
145 | public Obj()
146 | {
147 |
148 | }
149 |
150 | #endregion
151 |
152 | #region Public-Methods
153 |
154 | #endregion
155 |
156 | #region Private-Methods
157 |
158 | #endregion
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/Less3/Classes/ObjectAcl.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using Watson.ORM.Core;
10 |
11 | ///
12 | /// Access control list entry for an object.
13 | ///
14 | [Table("objectacls")]
15 | public class ObjectAcl
16 | {
17 | #region Public-Members
18 |
19 | ///
20 | /// Database identifier.
21 | ///
22 | [JsonIgnore]
23 | [Column("id", true, DataTypes.Int, false)]
24 | public int Id { get; set; } = 0;
25 |
26 | ///
27 | /// GUID.
28 | ///
29 | [Column("guid", false, DataTypes.Nvarchar, 64, false)]
30 | public string GUID { get; set; } = Guid.NewGuid().ToString();
31 |
32 | ///
33 | /// User group.
34 | ///
35 | [Column("usergroup", false, DataTypes.Nvarchar, 256, true)]
36 | public string UserGroup { get; set; } = null;
37 |
38 | ///
39 | /// User GUID.
40 | ///
41 | [Column("userguid", false, DataTypes.Nvarchar, 64, true)]
42 | public string UserGUID { get; set; } = null;
43 |
44 | ///
45 | /// GUID of the issuing user.
46 | ///
47 | [Column("issuedbyuserguid", false, DataTypes.Nvarchar, 64, true)]
48 | public string IssuedByUserGUID { get; set; } = null;
49 |
50 | ///
51 | /// GUID of the bucket.
52 | ///
53 | [Column("bucketguid", false, DataTypes.Nvarchar, 64, false)]
54 | public string BucketGUID { get; set; } = null;
55 |
56 | ///
57 | /// GUID of the object.
58 | ///
59 | [Column("objectguid", false, DataTypes.Nvarchar, 64, false)]
60 | public string ObjectGUID { get; set; } = null;
61 |
62 | ///
63 | /// Permit read operations.
64 | ///
65 | [Column("permitread", false, DataTypes.Boolean, false)]
66 | public bool PermitRead { get; set; } = false;
67 |
68 | ///
69 | /// Permit write operations.
70 | ///
71 | [Column("permitwrite", false, DataTypes.Boolean, false)]
72 | public bool PermitWrite { get; set; } = false;
73 |
74 | ///
75 | /// Permit access control read operations.
76 | ///
77 | [Column("permitreadacp", false, DataTypes.Boolean, false)]
78 | public bool PermitReadAcp { get; set; } = false;
79 |
80 | ///
81 | /// Permit access control write operations.
82 | ///
83 | [Column("permitwriteacp", false, DataTypes.Boolean, false)]
84 | public bool PermitWriteAcp { get; set; } = false;
85 |
86 | ///
87 | /// Permit full control.
88 | ///
89 | [Column("permitfullcontrol", false, DataTypes.Boolean, false)]
90 | public bool FullControl { get; set; } = false;
91 |
92 | ///
93 | /// Timestamp from record creation, in UTC time.
94 | ///
95 | [Column("createdutc", false, DataTypes.DateTime, 6, 6, false)]
96 | public DateTime CreatedUtc { get; set; } = DateTime.Now.ToUniversalTime();
97 |
98 | #endregion
99 |
100 | #region Private-Members
101 |
102 | #endregion
103 |
104 | #region Constructors-and-Factories
105 |
106 | ///
107 | /// Instantiate.
108 | ///
109 | public ObjectAcl()
110 | {
111 |
112 | }
113 |
114 | ///
115 | /// Create a group ACL.
116 | ///
117 | /// Group name.
118 | /// Issued by user GUID.
119 | /// Bucket GUID.
120 | /// Object GUID.
121 | /// Permit read.
122 | /// Permit write.
123 | /// Permit access control read.
124 | /// Permit access control write.
125 | /// Full control.
126 | /// Instance.
127 | public static ObjectAcl GroupAcl(
128 | string groupName,
129 | string issuedByUserGuid,
130 | string bucketGuid,
131 | string objectGuid,
132 | bool permitRead,
133 | bool permitWrite,
134 | bool permitReadAcp,
135 | bool permitWriteAcp,
136 | bool fullControl)
137 | {
138 | if (String.IsNullOrEmpty(groupName)) throw new ArgumentNullException(nameof(groupName));
139 | if (String.IsNullOrEmpty(issuedByUserGuid)) throw new ArgumentNullException(nameof(issuedByUserGuid));
140 | if (String.IsNullOrEmpty(bucketGuid)) throw new ArgumentNullException(nameof(bucketGuid));
141 | if (String.IsNullOrEmpty(objectGuid)) throw new ArgumentNullException(nameof(objectGuid));
142 |
143 | ObjectAcl ret = new ObjectAcl();
144 |
145 | ret.UserGroup = groupName;
146 | ret.UserGUID = null;
147 | ret.IssuedByUserGUID = issuedByUserGuid;
148 | ret.BucketGUID = bucketGuid;
149 | ret.ObjectGUID = objectGuid;
150 |
151 | ret.PermitRead = permitRead;
152 | ret.PermitWrite = permitWrite;
153 | ret.PermitReadAcp = permitReadAcp;
154 | ret.PermitWriteAcp = permitWriteAcp;
155 | ret.FullControl = fullControl;
156 |
157 | return ret;
158 | }
159 |
160 | ///
161 | /// Create a user ACL.
162 | ///
163 | /// User GUID.
164 | /// Issued by user GUID.
165 | /// Bucket GUID.
166 | /// Object GUID.
167 | /// Permit read.
168 | /// Permit write.
169 | /// Permit access control read.
170 | /// Permit access control write.
171 | /// Full control.
172 | /// Instance.
173 | public static ObjectAcl UserAcl(
174 | string userGuid,
175 | string issuedByUserGuid,
176 | string bucketGuid,
177 | string objectGuid,
178 | bool permitRead,
179 | bool permitWrite,
180 | bool permitReadAcp,
181 | bool permitWriteAcp,
182 | bool fullControl)
183 | {
184 | if (String.IsNullOrEmpty(userGuid)) throw new ArgumentNullException(nameof(userGuid));
185 | if (String.IsNullOrEmpty(issuedByUserGuid)) throw new ArgumentNullException(nameof(issuedByUserGuid));
186 | if (String.IsNullOrEmpty(objectGuid)) throw new ArgumentNullException(nameof(objectGuid));
187 |
188 | ObjectAcl ret = new ObjectAcl();
189 |
190 | ret.UserGroup = null;
191 | ret.UserGUID = userGuid;
192 | ret.IssuedByUserGUID = issuedByUserGuid;
193 | ret.BucketGUID = bucketGuid;
194 | ret.ObjectGUID = objectGuid;
195 |
196 | ret.PermitRead = permitRead;
197 | ret.PermitWrite = permitWrite;
198 | ret.PermitReadAcp = permitReadAcp;
199 | ret.PermitWriteAcp = permitWriteAcp;
200 | ret.FullControl = fullControl;
201 |
202 | return ret;
203 | }
204 |
205 | #endregion
206 |
207 | #region Public-Methods
208 |
209 | ///
210 | /// Create a human-readable string of the object.
211 | ///
212 | /// String.
213 | public override string ToString()
214 | {
215 | string
216 | ret = "--- Object ACL " + Id + " ---" + Environment.NewLine +
217 | " User group : " + UserGroup + Environment.NewLine +
218 | " User GUID : " + UserGUID + Environment.NewLine +
219 | " Issued by : " + IssuedByUserGUID + Environment.NewLine +
220 | " Bucket GUID : " + BucketGUID + Environment.NewLine +
221 | " Object GUID : " + ObjectGUID + Environment.NewLine +
222 | " Permissions : " + Environment.NewLine +
223 | " READ : " + PermitRead.ToString() + Environment.NewLine +
224 | " WRITE : " + PermitWrite.ToString() + Environment.NewLine +
225 | " READ_ACP : " + PermitReadAcp.ToString() + Environment.NewLine +
226 | " WRITE_ACP : " + PermitWriteAcp.ToString() + Environment.NewLine +
227 | " FULL_CONTROL : " + FullControl.ToString() + Environment.NewLine;
228 |
229 | return ret;
230 | }
231 |
232 | #endregion
233 |
234 | #region Private-Methods
235 |
236 | #endregion
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/src/Less3/Classes/ObjectTag.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using Watson.ORM.Core;
10 |
11 | ///
12 | /// Tag entry for an object.
13 | ///
14 | [Table("objecttags")]
15 | public class ObjectTag
16 | {
17 | #region Public-Members
18 |
19 | ///
20 | /// Database identifier.
21 | ///
22 | [JsonIgnore]
23 | [Column("id", true, DataTypes.Int, false)]
24 | public int Id { get; set; } = 0;
25 |
26 | ///
27 | /// GUID.
28 | ///
29 | [Column("guid", false, DataTypes.Nvarchar, 64, false)]
30 | public string GUID { get; set; } = Guid.NewGuid().ToString();
31 |
32 | ///
33 | /// GUID of the bucket.
34 | ///
35 | [Column("bucketguid", false, DataTypes.Nvarchar, 64, false)]
36 | public string BucketGUID { get; set; } = Guid.NewGuid().ToString();
37 |
38 | ///
39 | /// GUID of the object.
40 | ///
41 | [Column("objectguid", false, DataTypes.Nvarchar, 64, false)]
42 | public string ObjectGUID { get; set; } = Guid.NewGuid().ToString();
43 |
44 | ///
45 | /// Key.
46 | ///
47 | [Column("tagkey", false, DataTypes.Nvarchar, 256, false)]
48 | public string Key { get; set; } = null;
49 |
50 | ///
51 | /// Value.
52 | ///
53 | [Column("tagvalue", false, DataTypes.Nvarchar, 1024, true)]
54 | public string Value { get; set; } = null;
55 |
56 | ///
57 | /// Timestamp from record creation, in UTC time.
58 | ///
59 | [Column("createdutc", false, DataTypes.DateTime, 6, 6, false)]
60 | public DateTime CreatedUtc { get; set; } = DateTime.Now.ToUniversalTime();
61 |
62 | #endregion
63 |
64 | #region Private-Members
65 |
66 | #endregion
67 |
68 | #region Constructors-and-Factories
69 |
70 | ///
71 | /// Instantiate.
72 | ///
73 | public ObjectTag()
74 | {
75 |
76 | }
77 |
78 | ///
79 | /// Instantiate.
80 | ///
81 | /// Bucket GUID.
82 | /// Object GUID.
83 | /// Key.
84 | /// Value.
85 | public ObjectTag(string bucketGuid, string objectGuid, string key, string val)
86 | {
87 | if (String.IsNullOrEmpty(bucketGuid)) throw new ArgumentNullException(nameof(bucketGuid));
88 | if (String.IsNullOrEmpty(objectGuid)) throw new ArgumentNullException(nameof(objectGuid));
89 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
90 |
91 | BucketGUID = bucketGuid;
92 | ObjectGUID = objectGuid;
93 | Key = key;
94 | Value = val;
95 | }
96 |
97 | ///
98 | /// Instantiate.
99 | ///
100 | /// GUID.
101 | /// Bucket GUID.
102 | /// Object GUID.
103 | /// Key.
104 | /// Value.
105 | public ObjectTag(string guid, string bucketGuid, string objectGuid, string key, string val)
106 | {
107 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
108 | if (String.IsNullOrEmpty(bucketGuid)) throw new ArgumentNullException(nameof(bucketGuid));
109 | if (String.IsNullOrEmpty(objectGuid)) throw new ArgumentNullException(nameof(objectGuid));
110 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
111 |
112 | GUID = guid;
113 | BucketGUID = bucketGuid;
114 | ObjectGUID = objectGuid;
115 | Key = key;
116 | Value = val;
117 | }
118 |
119 | #endregion
120 |
121 | #region Public-Methods
122 |
123 | #endregion
124 |
125 | #region Private-Methods
126 |
127 | #endregion
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/Less3/Classes/RequestMetadata.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Text;
6 | using S3ServerLibrary;
7 |
8 | ///
9 | /// Less3 request metadata.
10 | ///
11 | public class RequestMetadata
12 | {
13 | #region Public-Members
14 |
15 | ///
16 | /// User.
17 | ///
18 | public User User = null;
19 |
20 | ///
21 | /// Credential.
22 | ///
23 | public Credential Credential = null;
24 |
25 | ///
26 | /// Bucket.
27 | ///
28 | public Bucket Bucket = null;
29 |
30 | ///
31 | /// Bucket client.
32 | ///
33 | internal BucketClient BucketClient = null;
34 |
35 | ///
36 | /// Bucket access control lists.
37 | ///
38 | public List BucketAcls = new List();
39 |
40 | ///
41 | /// Bucket tags.
42 | ///
43 | public List BucketTags = new List();
44 |
45 | ///
46 | /// Object.
47 | ///
48 | public Obj Obj = null;
49 |
50 | ///
51 | /// Object access control lists.
52 | ///
53 | public List ObjectAcls = new List();
54 |
55 | ///
56 | /// Object tags.
57 | ///
58 | public List ObjectTags = new List();
59 |
60 | ///
61 | /// Authentication result.
62 | ///
63 | public AuthenticationResult Authentication = AuthenticationResult.NotAuthenticated;
64 |
65 | ///
66 | /// Authorization result.
67 | ///
68 | public AuthorizationResult Authorization = AuthorizationResult.NotAuthorized;
69 |
70 | #endregion
71 |
72 | #region Private-Members
73 |
74 | #endregion
75 |
76 | #region Constructors-and-Factories
77 |
78 | ///
79 | /// Instantiate.
80 | ///
81 | public RequestMetadata()
82 | {
83 |
84 | }
85 |
86 | #endregion
87 |
88 | #region Public-Methods
89 |
90 | #endregion
91 |
92 | #region Private-Methods
93 |
94 | #endregion
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Less3/Classes/RetentionType.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Runtime.Serialization;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 |
10 | ///
11 | /// Type of object retention.
12 | ///
13 | [JsonConverter(typeof(JsonStringEnumConverter))]
14 | public enum RetentionType
15 | {
16 | ///
17 | /// No retention specified.
18 | ///
19 | [EnumMember(Value = "NONE")]
20 | NONE,
21 | ///
22 | /// Governance retention.
23 | ///
24 | [EnumMember(Value = "GOVERNANCE")]
25 | GOVERNANCE,
26 | ///
27 | /// Compliance retention.
28 | ///
29 | [EnumMember(Value = "COMPLIANCE")]
30 | COMPLIANCE
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Less3/Classes/Settings.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Net;
7 | using SyslogLogging;
8 | using DatabaseWrapper.Core;
9 | using Less3.Storage;
10 | using S3ServerLibrary;
11 | using Watson.ORM.Core;
12 | using WatsonWebserver.Core;
13 |
14 | ///
15 | /// Less3 settings.
16 | ///
17 | public class Settings
18 | {
19 | #region Public-Members
20 |
21 | ///
22 | /// Enable or disable the console.
23 | ///
24 | public bool EnableConsole { get; set; } = true;
25 |
26 | ///
27 | /// Enable or disable signature validation.
28 | ///
29 | public bool ValidateSignatures { get; set; } = true;
30 |
31 | ///
32 | /// Base domain, if using virtual hosted-style URLs, e.g. "localhost".
33 | ///
34 | public string BaseDomain { get; set; } = null;
35 |
36 | ///
37 | /// API key header for admin API requests.
38 | ///
39 | public string HeaderApiKey { get; set; } = "x-api-key";
40 |
41 | ///
42 | /// Admin API key.
43 | ///
44 | public string AdminApiKey { get; set; } = "less3admin";
45 |
46 | ///
47 | /// Region string.
48 | ///
49 | public string RegionString { get; set; } = "us-west-1";
50 |
51 | ///
52 | /// Database settings.
53 | ///
54 | public DatabaseSettings Database { get; set; } = new DatabaseSettings("./less3.db");
55 |
56 | ///
57 | /// Web server settings.
58 | ///
59 | public WebserverSettings Webserver { get; set; } = new WebserverSettings();
60 |
61 | ///
62 | /// Storage settings.
63 | ///
64 | public SettingsStorage Storage { get; set; } = new SettingsStorage();
65 |
66 | ///
67 | /// Logging settings.
68 | ///
69 | public SettingsLogging Logging { get; set; } = new SettingsLogging();
70 |
71 | ///
72 | /// Debugging settings.
73 | ///
74 | public SettingsDebug Debug { get; set; } = new SettingsDebug();
75 |
76 | #endregion
77 |
78 | #region Subordinate-Classes
79 |
80 | ///
81 | /// Storage settings.
82 | ///
83 | public class SettingsStorage
84 | {
85 | ///
86 | /// Temporary storage directory.
87 | ///
88 | public string TempDirectory { get; set; } = "./temp/";
89 |
90 | ///
91 | /// Type of storage driver.
92 | ///
93 | public StorageDriverType StorageType { get; set; } = StorageDriverType.Disk;
94 |
95 | ///
96 | /// Storage directory for 'Disk' StorageType.
97 | ///
98 | public string DiskDirectory { get; set; } = "./disk/";
99 | }
100 |
101 | ///
102 | /// Logging settings.
103 | ///
104 | public class SettingsLogging
105 | {
106 | ///
107 | /// IP address or hostname of the syslog server.
108 | ///
109 | public string SyslogServerIp { get; set; } = "127.0.0.1";
110 |
111 | ///
112 | /// Syslog server port number.
113 | ///
114 | public int SyslogServerPort { get; set; } = 514;
115 |
116 | ///
117 | /// Minimum log level severity.
118 | ///
119 | public Severity MinimumLevel { get; set; } = Severity.Info;
120 |
121 | ///
122 | /// Enable or disable logging of HTTP requests.
123 | ///
124 | public bool LogHttpRequests { get; set; } = false;
125 |
126 | ///
127 | /// Enable or disable logging of S3 requests.
128 | ///
129 | public bool LogS3Requests { get; set; } = false;
130 |
131 | ///
132 | /// Enable or disable logging of exceptions.
133 | ///
134 | public bool LogExceptions { get; set; } = false;
135 |
136 | ///
137 | /// Enable or disable logging of signature validation.
138 | ///
139 | public bool LogSignatureValidation { get; set; } = false;
140 |
141 | ///
142 | /// Enable or disable logging to the console.
143 | ///
144 | public bool ConsoleLogging { get; set; } = true;
145 |
146 | ///
147 | /// Enable or disable logging to disk.
148 | ///
149 | public bool DiskLogging { get; set; } = true;
150 |
151 | ///
152 | /// Directory on disk to write log files.
153 | ///
154 | public string DiskDirectory { get; set; } = "./logs/";
155 | }
156 |
157 | ///
158 | /// Debug settings.
159 | ///
160 | public class SettingsDebug
161 | {
162 | ///
163 | /// Enable or disable debugging of authentication logic.
164 | ///
165 | public bool Authentication { get; set; } = false;
166 |
167 | ///
168 | /// Enable or disable debugging of S3 request parsing.
169 | ///
170 | public bool S3Requests { get; set; } = false;
171 |
172 | ///
173 | /// Enable or disable debugging of exceptions.
174 | ///
175 | public bool Exceptions { get; set; } = false;
176 | }
177 |
178 | #endregion
179 |
180 | #region Constructors-and-Factories
181 |
182 | ///
183 | /// Instantiate.
184 | ///
185 | public Settings()
186 | {
187 |
188 | }
189 |
190 | ///
191 | /// Instantiate the object from a file.
192 | ///
193 | /// Filename.
194 | /// Settings.
195 | public static Settings FromFile(string filename)
196 | {
197 | if (String.IsNullOrEmpty(filename)) throw new ArgumentNullException(nameof(filename));
198 | if (!File.Exists(filename)) throw new FileNotFoundException(nameof(filename));
199 |
200 | string contents = Common.ReadTextFile(@filename);
201 | if (String.IsNullOrEmpty(contents))
202 | {
203 | Common.ExitApplication("Settings", "Unable to read contents of " + filename, -1);
204 | return null;
205 | }
206 |
207 | Settings ret = null;
208 |
209 | try
210 | {
211 | ret = SerializationHelper.DeserializeJson(contents);
212 | if (ret == null)
213 | {
214 | Common.ExitApplication("Settings", "Unable to deserialize " + filename + " (null)", -1);
215 | return null;
216 | }
217 | }
218 | catch (Exception)
219 | {
220 | Common.ExitApplication("Settings", "Unable to deserialize " + filename + " (exception)", -1);
221 | return null;
222 | }
223 |
224 | return ret;
225 | }
226 |
227 | #endregion
228 |
229 | #region Internal-Methods
230 |
231 | #endregion
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/Less3/Classes/User.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Classes
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 | using Watson.ORM.Core;
10 |
11 | ///
12 | /// User object.
13 | ///
14 | [Table("users")]
15 | public class User
16 | {
17 | #region Public-Members
18 |
19 | ///
20 | /// Database identifier.
21 | ///
22 | [JsonIgnore]
23 | [Column("id", true, DataTypes.Int, false)]
24 | public int Id { get; set; } = 0;
25 |
26 | ///
27 | /// GUID.
28 | ///
29 | [Column("guid", false, DataTypes.Nvarchar, 64, false)]
30 | public string GUID { get; set; } = Guid.NewGuid().ToString();
31 |
32 | ///
33 | /// Name of the user.
34 | ///
35 | [Column("name", false, DataTypes.Nvarchar, 256, false)]
36 | public string Name { get; set; } = null;
37 |
38 | ///
39 | /// Email address of the user.
40 | ///
41 | [Column("email", false, DataTypes.Nvarchar, 256, false)]
42 | public string Email { get; set; } = null;
43 |
44 | ///
45 | /// Timestamp from record creation, in UTC time.
46 | ///
47 | [Column("createdutc", false, DataTypes.DateTime, 6, 6, false)]
48 | public DateTime CreatedUtc { get; set; } = DateTime.Now.ToUniversalTime();
49 |
50 | #endregion
51 |
52 | #region Private-Members
53 |
54 | #endregion
55 |
56 | #region Constructors-and-Factories
57 |
58 | ///
59 | /// Instantiate.
60 | ///
61 | public User()
62 | {
63 |
64 | }
65 |
66 | ///
67 | /// Instantiate.
68 | ///
69 | /// Name.
70 | /// Email.
71 | public User(string name, string email)
72 | {
73 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
74 | if (String.IsNullOrEmpty(email)) throw new ArgumentNullException(nameof(email));
75 |
76 | GUID = Guid.NewGuid().ToString();
77 | Name = name;
78 | Email = email;
79 | }
80 |
81 | ///
82 | /// Instantiate.
83 | ///
84 | /// GUID.
85 | /// Name.
86 | /// Email.
87 | public User(string guid, string name, string email)
88 | {
89 | if (String.IsNullOrEmpty(guid)) throw new ArgumentNullException(nameof(guid));
90 | if (String.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));
91 | if (String.IsNullOrEmpty(email)) throw new ArgumentNullException(nameof(email));
92 |
93 | GUID = guid;
94 | Name = name;
95 | Email = email;
96 | }
97 |
98 | #endregion
99 |
100 | #region Public-Methods
101 |
102 | #endregion
103 |
104 | #region Private-Methods
105 |
106 | #endregion
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/Less3/Common.cs:
--------------------------------------------------------------------------------
1 | namespace Less3
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Data;
6 | using System.Dynamic;
7 | using System.IO;
8 | using System.IO.Compression;
9 | using System.Linq;
10 | using System.Security.AccessControl;
11 | using System.Security.Cryptography;
12 | using System.Security.Principal;
13 | using System.Text;
14 | using System.Text.Json;
15 | using System.Text.Json.Serialization;
16 | using System.Threading;
17 | using System.Threading.Tasks;
18 | using System.Xml;
19 | using System.Xml.Serialization;
20 | using XmlToPox;
21 |
22 | ///
23 | /// Common static methods.
24 | ///
25 | internal static class Common
26 | {
27 | #region Environment
28 |
29 | public static void ExitApplication(string method, string text, int returnCode)
30 | {
31 | Console.WriteLine("---");
32 | Console.WriteLine("");
33 | Console.WriteLine("The application has exited.");
34 | Console.WriteLine("");
35 | Console.WriteLine(" Requested by : " + method);
36 | Console.WriteLine(" Reason text : " + text);
37 | Console.WriteLine("");
38 | Console.WriteLine("---");
39 | Environment.Exit(returnCode);
40 | return;
41 | }
42 |
43 | #endregion
44 |
45 | #region Directory
46 |
47 | public static List GetSubdirectoryList(string directory, bool recursive)
48 | {
49 | try
50 | {
51 | /*
52 | * Prepends the 'directory' variable to the name of each directory already
53 | * so each is immediately usable from the resultant list
54 | *
55 | * Does NOT append a slash
56 | * Does NOT include the original directory in the list
57 | * Does NOT include child files
58 | *
59 | * i.e.
60 | * C:\code\kvpbase
61 | * C:\code\kvpbase\src
62 | * C:\code\kvpbase\test
63 | *
64 | */
65 |
66 | string[] folders;
67 |
68 | if (recursive)
69 | {
70 | folders = Directory.GetDirectories(@directory, "*", SearchOption.AllDirectories);
71 | }
72 | else
73 | {
74 | folders = Directory.GetDirectories(@directory, "*", SearchOption.TopDirectoryOnly);
75 | }
76 |
77 | List folderList = new List();
78 |
79 | foreach (string folder in folders)
80 | {
81 | folderList.Add(folder);
82 | }
83 |
84 | return folderList;
85 | }
86 | catch (Exception)
87 | {
88 | return null;
89 | }
90 | }
91 |
92 | public static bool WalkDirectory(
93 | string environment,
94 | int depth,
95 | string directory,
96 | bool prependFilename,
97 | out List subdirectories,
98 | out List files,
99 | out long bytes,
100 | bool recursive)
101 | {
102 | subdirectories = new List();
103 | files = new List();
104 | bytes = 0;
105 |
106 | try
107 | {
108 | subdirectories = Common.GetSubdirectoryList(directory, false);
109 | files = Common.GetFileList(environment, directory, prependFilename);
110 |
111 | if (files != null && files.Count > 0)
112 | {
113 | foreach (String currFile in files)
114 | {
115 | FileInfo fi = new FileInfo(currFile);
116 | bytes += fi.Length;
117 | }
118 | }
119 |
120 | List queueSubdirectories = new List();
121 | List queueFiles = new List();
122 | long queueBytes = 0;
123 |
124 | if (recursive)
125 | {
126 | if (subdirectories == null || subdirectories.Count < 1) return true;
127 | depth += 2;
128 |
129 | foreach (string curr in subdirectories)
130 | {
131 | List childSubdirectories = new List();
132 | List childFiles = new List();
133 | long childBytes = 0;
134 |
135 | WalkDirectory(
136 | environment,
137 | depth,
138 | curr,
139 | prependFilename,
140 | out childSubdirectories,
141 | out childFiles,
142 | out childBytes,
143 | true);
144 |
145 | if (childSubdirectories != null)
146 | foreach (string childSubdir in childSubdirectories)
147 | queueSubdirectories.Add(childSubdir);
148 |
149 | if (childFiles != null)
150 | foreach (string childFile in childFiles)
151 | queueFiles.Add(childFile);
152 |
153 | queueBytes += childBytes;
154 | }
155 | }
156 |
157 | if (queueSubdirectories != null)
158 | foreach (string queueSubdir in queueSubdirectories)
159 | subdirectories.Add(queueSubdir);
160 |
161 | if (queueFiles != null)
162 | foreach (string queueFile in queueFiles)
163 | files.Add(queueFile);
164 |
165 | bytes += queueBytes;
166 | return true;
167 | }
168 | catch (Exception)
169 | {
170 | return false;
171 | }
172 | }
173 |
174 | public static bool DirectoryStatistics(
175 | DirectoryInfo dirinfo,
176 | bool recursive,
177 | out long bytes,
178 | out int files,
179 | out int subdirs)
180 | {
181 | bytes = 0;
182 | files = 0;
183 | subdirs = 0;
184 |
185 | try
186 | {
187 | FileInfo[] fis = dirinfo.GetFiles();
188 | files = fis.Length;
189 |
190 | foreach (FileInfo fi in fis)
191 | {
192 | bytes += fi.Length;
193 | }
194 |
195 | // Add subdirectory sizes
196 | DirectoryInfo[] subdirinfos = dirinfo.GetDirectories();
197 |
198 | if (recursive)
199 | {
200 | foreach (DirectoryInfo subdirinfo in subdirinfos)
201 | {
202 | subdirs++;
203 | long subdirBytes = 0;
204 | int subdirFiles = 0;
205 | int subdirSubdirectories = 0;
206 |
207 | if (Common.DirectoryStatistics(subdirinfo, recursive, out subdirBytes, out subdirFiles, out subdirSubdirectories))
208 | {
209 | bytes += subdirBytes;
210 | files += subdirFiles;
211 | subdirs += subdirSubdirectories;
212 | }
213 | }
214 | }
215 | else
216 | {
217 | subdirs = subdirinfos.Length;
218 | }
219 |
220 | return true;
221 | }
222 | catch (Exception)
223 | {
224 | return false;
225 | }
226 | }
227 |
228 | #endregion
229 |
230 | #region File
231 |
232 | public static List GetFileList(string environment, string directory, bool prependFilename)
233 | {
234 | try
235 | {
236 | /*
237 | *
238 | * Returns only the filename unless prepend_filename is set
239 | * If prepend_filename is set, directory is prepended
240 | *
241 | */
242 |
243 | DirectoryInfo info = new DirectoryInfo(directory);
244 | FileInfo[] files = info.GetFiles().OrderBy(p => p.CreationTime).ToArray();
245 | List fileList = new List();
246 |
247 | foreach (FileInfo file in files)
248 | {
249 | if (prependFilename) fileList.Add(directory + "/" + file.Name);
250 | else fileList.Add(file.Name);
251 | }
252 |
253 | return fileList;
254 | }
255 | catch (Exception)
256 | {
257 | return null;
258 | }
259 | }
260 |
261 | public static bool WriteFile(string filename, string content, bool append)
262 | {
263 | using (StreamWriter writer = new StreamWriter(filename, append))
264 | {
265 | writer.WriteLine(content);
266 | }
267 | return true;
268 | }
269 |
270 | public static bool WriteFile(string filename, byte[] content)
271 | {
272 | if (content != null && content.Length > 0)
273 | {
274 | File.WriteAllBytes(filename, content);
275 | }
276 | else
277 | {
278 | File.Create(filename).Close();
279 | }
280 |
281 | return true;
282 | }
283 |
284 | public static string ReadTextFile(string filename)
285 | {
286 | try
287 | {
288 | return File.ReadAllText(@filename);
289 | }
290 | catch (Exception)
291 | {
292 | return null;
293 | }
294 | }
295 |
296 | public static byte[] ReadBinaryFile(string filename)
297 | {
298 | try
299 | {
300 | return File.ReadAllBytes(@filename);
301 | }
302 | catch (Exception)
303 | {
304 | return null;
305 | }
306 | }
307 |
308 | #endregion
309 |
310 | #region Misc
311 |
312 | public static string Line(int count, string fill)
313 | {
314 | if (count < 1) return "";
315 |
316 | string ret = "";
317 | for (int i = 0; i < count; i++)
318 | {
319 | ret += fill;
320 | }
321 |
322 | return ret;
323 | }
324 |
325 | public static bool IsLaterThanNow(DateTime dt)
326 | {
327 | if (DateTime.Compare(dt, DateTime.Now) > 0)
328 | {
329 | return true;
330 | }
331 | else
332 | {
333 | return false;
334 | }
335 | }
336 |
337 | public static bool ContainsUnsafeCharacters(string data)
338 | {
339 | /*
340 | *
341 | * Returns true if unsafe characters exist
342 | *
343 | *
344 | */
345 |
346 | // see https://kb.acronis.com/content/39790
347 |
348 | if (String.IsNullOrEmpty(data)) return false;
349 | if (data.Equals(".")) return true;
350 | if (data.Equals("..")) return true;
351 |
352 | if (
353 | (String.Compare(data.ToLower(), "com1") == 0) ||
354 | (String.Compare(data.ToLower(), "com2") == 0) ||
355 | (String.Compare(data.ToLower(), "com3") == 0) ||
356 | (String.Compare(data.ToLower(), "com4") == 0) ||
357 | (String.Compare(data.ToLower(), "com5") == 0) ||
358 | (String.Compare(data.ToLower(), "com6") == 0) ||
359 | (String.Compare(data.ToLower(), "com7") == 0) ||
360 | (String.Compare(data.ToLower(), "com8") == 0) ||
361 | (String.Compare(data.ToLower(), "com9") == 0) ||
362 | (String.Compare(data.ToLower(), "lpt1") == 0) ||
363 | (String.Compare(data.ToLower(), "lpt2") == 0) ||
364 | (String.Compare(data.ToLower(), "lpt3") == 0) ||
365 | (String.Compare(data.ToLower(), "lpt4") == 0) ||
366 | (String.Compare(data.ToLower(), "lpt5") == 0) ||
367 | (String.Compare(data.ToLower(), "lpt6") == 0) ||
368 | (String.Compare(data.ToLower(), "lpt7") == 0) ||
369 | (String.Compare(data.ToLower(), "lpt8") == 0) ||
370 | (String.Compare(data.ToLower(), "lpt9") == 0) ||
371 | (String.Compare(data.ToLower(), "con") == 0) ||
372 | (String.Compare(data.ToLower(), "nul") == 0) ||
373 | (String.Compare(data.ToLower(), "prn") == 0) ||
374 | (String.Compare(data.ToLower(), "con") == 0)
375 | )
376 | {
377 | return true;
378 | }
379 |
380 | for (int i = 0; i < data.Length; i++)
381 | {
382 | if (
383 | ((int)(data[i]) < 32) || // below range
384 | ((int)(data[i]) > 126) || // above range
385 | ((int)(data[i]) == 47) || // slash /
386 | ((int)(data[i]) == 92) || // backslash \
387 | ((int)(data[i]) == 63) || // question mark ?
388 | ((int)(data[i]) == 60) || // less than <
389 | ((int)(data[i]) == 62) || // greater than >
390 | ((int)(data[i]) == 58) || // colon :
391 | ((int)(data[i]) == 42) || // asterisk *
392 | ((int)(data[i]) == 124) || // pipe |
393 | ((int)(data[i]) == 34) || // double quote "
394 | ((int)(data[i]) == 39) || // single quote '
395 | ((int)(data[i]) == 94) // caret ^
396 | )
397 | {
398 | return true;
399 | }
400 | }
401 |
402 | return false;
403 | }
404 |
405 | #endregion
406 |
407 | #region Crypto
408 |
409 | public static byte[] Md5(byte[] data)
410 | {
411 | if (data == null) return null;
412 | return MD5.Create().ComputeHash(data);
413 | }
414 |
415 | public static byte[] Md5(Stream stream)
416 | {
417 | if (stream == null || !stream.CanRead) return null;
418 |
419 | MD5 md5 = MD5.Create();
420 | return md5.ComputeHash(stream);
421 | }
422 |
423 | public static async Task Md5Async(Stream stream, int bufferSize)
424 | {
425 | using (var md5 = MD5.Create())
426 | {
427 | byte[] buffer = new byte[bufferSize];
428 | int read = 0;
429 |
430 | do
431 | {
432 | read = await stream.ReadAsync(buffer, 0, bufferSize);
433 | if (read > 0)
434 | {
435 | md5.TransformBlock(buffer, 0, read, null, 0);
436 | }
437 | } while (read > 0);
438 |
439 | md5.TransformFinalBlock(buffer, 0, 0);
440 | return md5.Hash;
441 | }
442 | }
443 |
444 | #endregion
445 |
446 | #region Encoding
447 |
448 | public static byte[] StreamToBytes(Stream input)
449 | {
450 | if (input == null) throw new ArgumentNullException(nameof(input));
451 | if (!input.CanRead) throw new InvalidOperationException("Input stream is not readable");
452 |
453 | byte[] buffer = new byte[16 * 1024];
454 | using (MemoryStream ms = new MemoryStream())
455 | {
456 | int read;
457 |
458 | while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
459 | {
460 | ms.Write(buffer, 0, read);
461 | }
462 |
463 | return ms.ToArray();
464 | }
465 | }
466 |
467 | public static string Base64ToString(string data)
468 | {
469 | if (String.IsNullOrEmpty(data)) return null;
470 | byte[] bytes = System.Convert.FromBase64String(data);
471 | return System.Text.UTF8Encoding.UTF8.GetString(bytes);
472 | }
473 |
474 | public static string StringToBase64(string data)
475 | {
476 | if (String.IsNullOrEmpty(data)) return null;
477 | byte[] bytes = System.Text.UTF8Encoding.UTF8.GetBytes(data);
478 | return System.Convert.ToBase64String(bytes);
479 | }
480 |
481 | public static string BytesToHexString(byte[] ba)
482 | {
483 | return BitConverter.ToString(ba).Replace("-", "");
484 | }
485 |
486 | #endregion
487 | }
488 | }
489 |
--------------------------------------------------------------------------------
/src/Less3/Dockerbuild.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 | IF "%1" == "" GOTO :Usage
3 | IF "%2" == "" GOTO :Usage
4 | ECHO.
5 | ECHO Building for linux/amd64 and linux/arm64/v8...
6 | docker buildx build -f Dockerfile --platform linux/amd64,linux/arm64/v8 --tag jchristn/less3:%1 --load .
7 |
8 | IF "%2" NEQ "1" GOTO :Done
9 |
10 | ECHO.
11 | ECHO Pushing image...
12 | docker push jchristn/less3:%1
13 |
14 | GOTO :Done
15 |
16 | :Usage
17 | ECHO.
18 | ECHO Provide two arguments; the first being the version and the second being 1 or 0 to indicate whether or not to push.
19 | ECHO Example: dockerbuild.bat v2.0.0 1
20 |
21 | :Done
22 | ECHO.
23 | ECHO Done
24 | @ECHO ON
25 |
--------------------------------------------------------------------------------
/src/Less3/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env
2 | WORKDIR /app
3 |
4 | # Copy everything from source directory into /App
5 | COPY . ./
6 | EXPOSE 8000
7 | ENTRYPOINT ["dotnet", "Less3.dll"]
8 |
--------------------------------------------------------------------------------
/src/Less3/Dockerrun.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | IF "%1" == "" GOTO :Usage
4 |
5 | if not exist system.json (
6 | echo Configuration file system.json not found.
7 | exit /b 1
8 | )
9 |
10 | REM Items that require persistence
11 | REM system.json
12 | REM less3.db
13 | REM logs/
14 | REM temp/
15 |
16 | REM Argument order matters!
17 |
18 | docker run ^
19 | -p 8000:8000 ^
20 | -t ^
21 | -i ^
22 | -e "TERM=xterm-256color" ^
23 | -v .\system.json:/app/system.json ^
24 | -v .\less3.db:/app/less3.db ^
25 | -v .\logs\:/app/logs/ ^
26 | -v .\temp\:/app/temp/ ^
27 | jchristn/less3:%1
28 |
29 | GOTO :Done
30 |
31 | :Usage
32 | ECHO Provide one argument indicating the tag.
33 | ECHO Example: dockerrun.bat v2.1.11
34 | :Done
35 | @echo on
36 |
--------------------------------------------------------------------------------
/src/Less3/Dockerrun.sh:
--------------------------------------------------------------------------------
1 | if [ -z "${IMG_TAG}" ]; then
2 | IMG_TAG='v2.1.11'
3 | fi
4 |
5 | echo Using image tag $IMG_TAG
6 |
7 | if [ ! -f "system.json" ]
8 | then
9 | echo Configuration file system.json not found.
10 | exit
11 | fi
12 |
13 | # Items that require persistence
14 | # system.json
15 | # less3.db
16 | # logs/
17 | # temp/
18 |
19 | # Argument order matters!
20 |
21 | docker run \
22 | -p 8000:8000 \
23 | -t \
24 | -i \
25 | -e "TERM=xterm-256color" \
26 | -v ./system.json:/app/system.json \
27 | -v ./less3.db:/app/less3.db \
28 | -v ./logs/:/app/logs/ \
29 | -v ./temp/:/app/temp/ \
30 | jchristn/less3:$IMG_TAG
31 |
--------------------------------------------------------------------------------
/src/Less3/LICENSE.md:
--------------------------------------------------------------------------------
1 | # The MIT License (MIT)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/src/Less3/Less3.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | netstandard2.0;netstandard2.1;net6.0;net8.0
6 | 2.1.13
7 | true
8 | Joel Christner
9 | Joel Christner
10 | Less3
11 | <3 Less3 is S3-compatible object storage that you can run on your laptop, server, or anywhere you like.
12 | (c)2024 Joel Christner
13 |
14 | https://github.com/jchristn/less3
15 |
16 | https://github.com/jchristn/less3
17 | Github
18 | S3 storage blob object storage rest restful
19 | Dependency update, validation with AWS CLI
20 | LICENSE.md
21 | favicon.png
22 | README.md
23 | heart.ico
24 |
25 |
26 |
27 | Less3.xml
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | Always
45 |
46 |
47 | Always
48 |
49 |
50 | Always
51 |
52 |
53 | Always
54 |
55 |
56 | Always
57 |
58 |
59 | Always
60 |
61 |
62 | True
63 | \
64 |
65 |
66 | True
67 |
68 |
69 |
70 | True
71 |
72 |
73 |
74 | Always
75 |
76 |
77 | Always
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/src/Less3/Storage/DiskStorageDriver.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Storage
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Security.Cryptography;
7 | using System.Text;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Less3.Classes;
11 |
12 | ///
13 | /// Disk storage driver.
14 | ///
15 | public class DiskStorageDriver : StorageDriver
16 | {
17 | #region Public-Members
18 |
19 | ///
20 | /// Buffer size to use while reading file streams.
21 | ///
22 | public int StreamBufferSize
23 | {
24 | get
25 | {
26 | return _StreamBufferSize;
27 | }
28 | set
29 | {
30 | if (value < 1) throw new ArgumentException("Stream buffer size must be greater than zero bytes.");
31 | _StreamBufferSize = value;
32 | }
33 | }
34 |
35 | #endregion
36 |
37 | #region Private-Members
38 |
39 | private CancellationTokenSource _TokenSource = new CancellationTokenSource();
40 | private CancellationToken _Token;
41 | private string _BaseDirectory = null;
42 | private int _StreamBufferSize = 65536;
43 |
44 | #endregion
45 |
46 | #region Constructors-and-Factories
47 |
48 | ///
49 | /// Instantiate.
50 | ///
51 | /// Base directory.
52 | public DiskStorageDriver(string baseDirectory)
53 | {
54 | if (String.IsNullOrEmpty(baseDirectory)) throw new ArgumentNullException(nameof(baseDirectory));
55 | if (!Directory.Exists(baseDirectory)) Directory.CreateDirectory(baseDirectory);
56 |
57 | baseDirectory = baseDirectory.Replace('\\', '/');
58 | if (!baseDirectory.EndsWith("/")) baseDirectory += "/";
59 |
60 | _BaseDirectory = baseDirectory;
61 | _Token = _TokenSource.Token;
62 | }
63 |
64 | #endregion
65 |
66 | #region Public-Methods
67 |
68 | ///
69 | /// Delete an object by key.
70 | ///
71 | /// Key.
72 | public override void Delete(string key)
73 | {
74 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
75 | string file = FilePath(key);
76 | if (File.Exists(file)) File.Delete(file);
77 | }
78 |
79 | ///
80 | /// Verify the existence of an object by key.
81 | ///
82 | /// Key.
83 | /// True if exists.
84 | public override bool Exists(string key)
85 | {
86 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
87 | string file = FilePath(key);
88 | if (File.Exists(file)) return true;
89 | return false;
90 | }
91 |
92 | ///
93 | /// Read an object.
94 | ///
95 | /// Key.
96 | /// Data.
97 | public override byte[] Read(string key)
98 | {
99 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
100 | string file = FilePath(key);
101 | return File.ReadAllBytes(file);
102 | }
103 |
104 | ///
105 | /// Read an object asynchronously.
106 | ///
107 | /// Key.
108 | /// Data.
109 | public override async Task ReadAsync(string key)
110 | {
111 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
112 | string file = FilePath(key);
113 |
114 | using (FileStream fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true))
115 | {
116 | var length = (int)fileStream.Length;
117 | var bytes = new byte[length];
118 | await fileStream.ReadAsync(bytes, 0, length);
119 | return bytes;
120 | }
121 | }
122 |
123 | ///
124 | /// Read an object.
125 | /// Your code must close the stream when complete.
126 | ///
127 | /// Key.
128 | /// ObjectStream.
129 | public override ObjectStream ReadStream(string key)
130 | {
131 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
132 | string file = FilePath(key);
133 | FileInfo fi = new FileInfo(file);
134 | long contentLength = fi.Length;
135 | FileStream stream = new FileStream(file, FileMode.Open);
136 | stream.Seek(0, SeekOrigin.Begin);
137 | return new ObjectStream(key, fi.Length, stream);
138 | }
139 |
140 | ///
141 | /// Read a specific number of bytes from a specific location in an object.
142 | ///
143 | /// Key.
144 | /// Starting position.
145 | /// Number of bytes to read.
146 | /// Data.
147 | public override byte[] ReadRange(string key, long indexStart, long count)
148 | {
149 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
150 | if (indexStart < 0) throw new ArgumentException("Index start must be zero or greater.");
151 | if (count < 0) throw new ArgumentException("Count must be zero or greater.");
152 |
153 | string file = FilePath(key);
154 | FileInfo fi = new FileInfo(file);
155 | long contentLength = fi.Length;
156 |
157 | if (indexStart + count > contentLength) throw new ArgumentException("Index start combined with count must not result in a position that exceeds the size of the file.");
158 |
159 | using (FileStream fs = new FileStream(file, FileMode.Open))
160 | {
161 | long bytesRemaining = count;
162 | int read = 0;
163 | byte[] buffer = null;
164 |
165 | using (MemoryStream ms = new MemoryStream())
166 | {
167 | while (bytesRemaining > 0)
168 | {
169 | if (bytesRemaining > _StreamBufferSize) buffer = new byte[_StreamBufferSize];
170 | else buffer = new byte[bytesRemaining];
171 |
172 | read = fs.Read(buffer, 0, buffer.Length);
173 | if (read > 0)
174 | {
175 | ms.Write(buffer, 0, read);
176 | bytesRemaining -= read;
177 | }
178 | }
179 |
180 | return ms.ToArray();
181 | }
182 | }
183 | }
184 |
185 | ///
186 | /// Read a specific number of bytes asynchronously from a specific location in an object.
187 | ///
188 | /// Key.
189 | /// Starting position.
190 | /// Number of bytes to read.
191 | /// Data.
192 | public override async Task ReadRangeAsync(string key, long indexStart, long count)
193 | {
194 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
195 | if (indexStart < 0) throw new ArgumentException("Index start must be zero or greater.");
196 | if (count < 0) throw new ArgumentException("Count must be zero or greater.");
197 |
198 | string file = FilePath(key);
199 | FileInfo fi = new FileInfo(file);
200 | long contentLength = fi.Length;
201 |
202 | if (indexStart + count > contentLength) throw new ArgumentException("Index start combined with count must not result in a position that exceeds the size of the file.");
203 |
204 | using (FileStream fs = new FileStream(file, FileMode.Open))
205 | {
206 | long bytesRemaining = count;
207 | int read = 0;
208 | byte[] buffer = null;
209 |
210 | using (MemoryStream ms = new MemoryStream())
211 | {
212 | while (bytesRemaining > 0)
213 | {
214 | if (bytesRemaining > _StreamBufferSize) buffer = new byte[_StreamBufferSize];
215 | else buffer = new byte[bytesRemaining];
216 |
217 | read = await fs.ReadAsync(buffer, 0, buffer.Length);
218 | if (read > 0)
219 | {
220 | await ms.WriteAsync(buffer, 0, read);
221 | bytesRemaining -= read;
222 | }
223 | }
224 |
225 | return ms.ToArray();
226 | }
227 | }
228 | }
229 |
230 | ///
231 | /// Read a specific number of bytes from a specific location in an object.
232 | /// Your code must close the stream when complete.
233 | ///
234 | /// Key.
235 | /// Starting position.
236 | /// Number of bytes to read.
237 | /// ObjectStream.
238 | public override ObjectStream ReadRangeStream(string key, long indexStart, long count)
239 | {
240 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
241 | if (indexStart < 0) throw new ArgumentException("Index start must be zero or greater.");
242 | if (count < 0) throw new ArgumentException("Count must be zero or greater.");
243 |
244 | string file = FilePath(key);
245 | FileInfo fi = new FileInfo(file);
246 | long contentLength = fi.Length;
247 |
248 | if (indexStart + count > contentLength) throw new ArgumentException("Index start combined with count must not result in a position that exceeds the size of the file.");
249 |
250 | using (FileStream fs = new FileStream(file, FileMode.Open))
251 | {
252 | long bytesRemaining = count;
253 | int read = 0;
254 | byte[] buffer = null;
255 | Stream ms = new MemoryStream();
256 |
257 | while (bytesRemaining > 0)
258 | {
259 | if (bytesRemaining > _StreamBufferSize) buffer = new byte[_StreamBufferSize];
260 | else buffer = new byte[bytesRemaining];
261 |
262 | read = fs.Read(buffer, 0, buffer.Length);
263 | if (read > 0)
264 | {
265 | ms.Write(buffer, 0, read);
266 | bytesRemaining -= read;
267 | }
268 | }
269 |
270 | ms.Seek(0, SeekOrigin.Begin);
271 | return new ObjectStream(key, count, ms);
272 | }
273 | }
274 |
275 | ///
276 | /// Read a specific number of bytes asynchronously from a specific location in an object.
277 | /// Your code must close the stream when complete.
278 | ///
279 | /// Key.
280 | /// Starting position.
281 | /// Number of bytes to read.
282 | /// ObjectStream.
283 | public override async Task ReadRangeStreamAsync(string key, long indexStart, long count)
284 | {
285 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
286 | if (indexStart < 0) throw new ArgumentException("Index start must be zero or greater.");
287 | if (count < 0) throw new ArgumentException("Count must be zero or greater.");
288 |
289 | string file = FilePath(key);
290 | FileInfo fi = new FileInfo(file);
291 | long contentLength = fi.Length;
292 |
293 | if (indexStart + count > contentLength) throw new ArgumentException("Index start combined with count must not result in a position that exceeds the size of the file.");
294 |
295 | using (FileStream fs = new FileStream(file, FileMode.Open))
296 | {
297 | long bytesRemaining = count;
298 | int read = 0;
299 | byte[] buffer = null;
300 | Stream ms = new MemoryStream();
301 |
302 | while (bytesRemaining > 0)
303 | {
304 | if (bytesRemaining > _StreamBufferSize) buffer = new byte[_StreamBufferSize];
305 | else buffer = new byte[bytesRemaining];
306 |
307 | read = await fs.ReadAsync(buffer, 0, buffer.Length);
308 | if (read > 0)
309 | {
310 | await ms.WriteAsync(buffer, 0, read);
311 | bytesRemaining -= read;
312 | }
313 | }
314 |
315 | ms.Seek(0, SeekOrigin.Begin);
316 | return new ObjectStream(key, count, ms);
317 | }
318 | }
319 |
320 | ///
321 | /// Write an object.
322 | ///
323 | /// Key.
324 | /// Data.
325 | /// MD5 hash.
326 | public override byte[] Write(string key, byte[] data)
327 | {
328 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
329 | if (data == null) data = new byte[0];
330 | MemoryStream ms = new MemoryStream(data);
331 | ms.Seek(0, SeekOrigin.Begin);
332 | return Write(key, data.Length, ms);
333 | }
334 |
335 | ///
336 | /// Write an object asynchronously.
337 | ///
338 | /// Key.
339 | /// Data.
340 | /// MD5 hash.
341 | public override async Task WriteAsync(string key, byte[] data)
342 | {
343 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
344 | if (data == null) data = new byte[0];
345 | MemoryStream ms = new MemoryStream(data);
346 | ms.Seek(0, SeekOrigin.Begin);
347 | return await WriteAsync(key, data.Length, ms);
348 | }
349 |
350 | ///
351 | /// Write an object.
352 | ///
353 | /// Key.
354 | /// Number of bytes to read from the stream.
355 | /// Stream.
356 | /// MD5 hash.
357 | public override byte[] Write(string key, long contentLength, Stream stream)
358 | {
359 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
360 |
361 | string file = FilePath(key);
362 | using (FileStream fs = new FileStream(file, FileMode.Create))
363 | {
364 | long bytesRemaining = contentLength;
365 | int read = 0;
366 | byte[] buffer = new byte[_StreamBufferSize];
367 |
368 | while (bytesRemaining > 0)
369 | {
370 | read = stream.Read(buffer, 0, buffer.Length);
371 | if (read > 0)
372 | {
373 | fs.Write(buffer, 0, read);
374 | bytesRemaining -= read;
375 | }
376 | }
377 | }
378 |
379 | FileInfo fi = new FileInfo(file);
380 |
381 | using (FileStream fs = new FileStream(file, FileMode.Open))
382 | {
383 | return Common.Md5(fs);
384 | }
385 | }
386 |
387 | ///
388 | /// Write an object asynchronously.
389 | ///
390 | /// Key.
391 | /// Number of bytes to read from the stream.
392 | /// Stream.
393 | /// MD5 hash.
394 | public override async Task WriteAsync(string key, long contentLength, Stream stream)
395 | {
396 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
397 |
398 | string file = FilePath(key);
399 | using (FileStream fs = new FileStream(file, FileMode.Create))
400 | {
401 | long bytesRemaining = contentLength;
402 | int read = 0;
403 | byte[] buffer = new byte[_StreamBufferSize];
404 |
405 | while (bytesRemaining > 0)
406 | {
407 | read = await stream.ReadAsync(buffer, 0, buffer.Length);
408 | if (read > 0)
409 | {
410 | await fs.WriteAsync(buffer, 0, read);
411 | bytesRemaining -= read;
412 | }
413 | }
414 | }
415 |
416 | FileInfo fi = new FileInfo(file);
417 |
418 | using (FileStream fs = new FileStream(file, FileMode.Open))
419 | {
420 | return await Common.Md5Async(fs, _StreamBufferSize);
421 | }
422 | }
423 |
424 | #endregion
425 |
426 | #region Private-Methods
427 |
428 | private string FilePath(string key)
429 | {
430 | return _BaseDirectory + key;
431 | }
432 |
433 | #endregion
434 | }
435 | }
436 |
--------------------------------------------------------------------------------
/src/Less3/Storage/ObjectStream.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Storage
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Text;
7 |
8 | ///
9 | /// Stream of data for an object.
10 | ///
11 | public class ObjectStream
12 | {
13 | #region Public-Members
14 |
15 | ///
16 | /// Object key.
17 | ///
18 | public string Key { get; set; }
19 |
20 | ///
21 | /// Content length.
22 | ///
23 | public long ContentLength { get; set; }
24 |
25 | ///
26 | /// Stream containing data.
27 | ///
28 | public Stream Data { get; set; }
29 |
30 | #endregion
31 |
32 | #region Constructors-and-Factories
33 |
34 | ///
35 | /// Instantiate.
36 | ///
37 | /// Object key.
38 | /// Content length.
39 | /// Stream containing data.
40 | public ObjectStream(string key, long contentLength, Stream data)
41 | {
42 | if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
43 | if (contentLength < 0) throw new ArgumentException("Content length must be zero or greater.");
44 | if (data == null) throw new ArgumentNullException(nameof(data));
45 |
46 | Key = key;
47 | ContentLength = contentLength;
48 | Data = data;
49 | }
50 |
51 | #endregion
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Less3/Storage/StorageDriver.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Storage
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | ///
10 | /// Less3 storage driver; allows developers to build their own storage providers for Less3.
11 | ///
12 | public abstract class StorageDriver
13 | {
14 | #region Public-Methods
15 |
16 | ///
17 | /// Delete an object by key.
18 | ///
19 | /// Key.
20 | public abstract void Delete(string key);
21 |
22 | ///
23 | /// Verify the existence of an object by key.
24 | ///
25 | /// Key.
26 | /// True if exists.
27 | public abstract bool Exists(string key);
28 |
29 | ///
30 | /// Read an object.
31 | ///
32 | /// Key.
33 | /// Data.
34 | public abstract byte[] Read(string key);
35 |
36 | ///
37 | /// Read an object asynchronously.
38 | ///
39 | /// Key.
40 | /// Data.
41 | public abstract Task ReadAsync(string key);
42 |
43 | ///
44 | /// Read an object.
45 | ///
46 | /// Key.
47 | /// ObjectStream.
48 | public abstract ObjectStream ReadStream(string key);
49 |
50 | ///
51 | /// Read a specific number of bytes from a specific location in an object.
52 | ///
53 | /// Key.
54 | /// Starting position.
55 | /// Number of bytes to read.
56 | /// Data.
57 | public abstract byte[] ReadRange(string key, long indexStart, long count);
58 |
59 | ///
60 | /// Read a specific number of bytes asynchronously from a specific location in an object.
61 | ///
62 | /// Key.
63 | /// Starting position.
64 | /// Number of bytes to read.
65 | /// Data.
66 | public abstract Task ReadRangeAsync(string key, long indexStart, long count);
67 |
68 | ///
69 | /// Read a specific number of bytes from a specific location in an object.
70 | ///
71 | /// Key.
72 | /// Starting position.
73 | /// Number of bytes to read.
74 | /// ObjectStream.
75 | public abstract ObjectStream ReadRangeStream(string key, long indexStart, long count);
76 |
77 | ///
78 | /// Read a specific number of bytes asynchronously from a specific location in an object.
79 | ///
80 | /// Key.
81 | /// Starting position.
82 | /// Number of bytes to read.
83 | /// ObjectStream.
84 | public abstract Task ReadRangeStreamAsync(string key, long indexStart, long count);
85 |
86 | ///
87 | /// Write an object.
88 | ///
89 | /// Key.
90 | /// Data.
91 | /// MD5 hash.
92 | public abstract byte[] Write(string key, byte[] data);
93 |
94 | ///
95 | /// Write an object asynchronously.
96 | ///
97 | /// Key.
98 | /// Data.
99 | /// MD5 hash.
100 | public abstract Task WriteAsync(string key, byte[] data);
101 |
102 | ///
103 | /// Write an object.
104 | ///
105 | /// Key.
106 | /// Number of bytes to read from the stream.
107 | /// Stream.
108 | /// MD5 hash.
109 | public abstract byte[] Write(string key, long contentLength, Stream stream);
110 |
111 | ///
112 | /// Write an object asynchronously.
113 | ///
114 | /// Key.
115 | /// Number of bytes to read from the stream.
116 | /// Stream.
117 | /// MD5 hash.
118 | public abstract Task WriteAsync(string key, long contentLength, Stream stream);
119 |
120 | #endregion
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Less3/Storage/StorageDriverType.cs:
--------------------------------------------------------------------------------
1 | namespace Less3.Storage
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Runtime.Serialization;
6 | using System.Text;
7 | using System.Text.Json;
8 | using System.Text.Json.Serialization;
9 |
10 | ///
11 | /// Type of storage driver.
12 | ///
13 | [JsonConverter(typeof(JsonStringEnumConverter))]
14 | public enum StorageDriverType
15 | {
16 | ///
17 | /// Disk.
18 | ///
19 | [EnumMember(Value = "Disk")]
20 | Disk
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Less3/clean.bat:
--------------------------------------------------------------------------------
1 | del /q /f logs\
2 | del /q /f temp\
3 | del /q /f disk\
4 | del /q /f system.json
5 | del /q /f less3.db
6 |
--------------------------------------------------------------------------------
/src/Less3/clean.sh:
--------------------------------------------------------------------------------
1 | rm -rf logs\
2 | rm -rf temp\
3 | rm -rf disk\
4 | rm -rf system.json
5 | rm -rf less3.db
6 |
--------------------------------------------------------------------------------
/src/Less3/heart.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jchristn/Less3/b973c87d9ffe0d1c988593c472d33924e74207d9/src/Less3/heart.ico
--------------------------------------------------------------------------------