Compare commits
	
		
			37 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ccc1f73e52 | |||
| b48d1347c7 | |||
| bed8a88b09 | |||
| 0a70d99af5 | |||
| 6720cbc0d9 | |||
| 8d11b7c434 | |||
| 147608a327 | |||
| 951c87b63f | |||
| 25180afaa2 | |||
| 33ca03b9d7 | |||
| b562ca4bd4 | |||
| a25dc5827b | |||
| 5263f96956 | |||
| 2b2add62b2 | |||
| fdc93e6485 | |||
| b08f1c33d5 | |||
| 524f37d9fe | |||
| c958c57794 | |||
| 8fbad2a1e0 | |||
| 989cd195f8 | |||
| cb4933fa72 | |||
| f504ab5929 | |||
| f8e95a8037 | |||
| feaa51cfcd | |||
| ddf76aacc5 | |||
| ca8a0d55ba | |||
| fe4aace777 | |||
| be864235cb | |||
| dba699524f | |||
| 7950ca3004 | |||
| 6e5ca0c61c | |||
| 7255cd03cb | |||
| 3cf4986418 | |||
| 0a338c4568 | |||
| 0fbf401fd3 | |||
| 48ca68fafd | |||
| f3e594d307 | 
							
								
								
									
										2
									
								
								.hgignore → .gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.hgignore → .gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,3 @@
 | 
			
		||||
syntax: glob
 | 
			
		||||
 | 
			
		||||
cmd/contented/contented
 | 
			
		||||
build/
 | 
			
		||||
_dist/
 | 
			
		||||
							
								
								
									
										11
									
								
								.hgtags
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								.hgtags
									
									
									
									
									
								
							@@ -1,11 +0,0 @@
 | 
			
		||||
e2250a7fd29052ea767f18e1459cabea4cd7efd3 release-1.0.0
 | 
			
		||||
b8975b9e75648a7c2a5003c67db92cf2216e01c0 release-1.0.1
 | 
			
		||||
c7b699105bd166e7a01aa0e678d34624680bf81e release-1.1.0
 | 
			
		||||
c7b699105bd166e7a01aa0e678d34624680bf81e release-1.1.0
 | 
			
		||||
0000000000000000000000000000000000000000 release-1.1.0
 | 
			
		||||
0000000000000000000000000000000000000000 release-1.1.0
 | 
			
		||||
cfb1e028fd0627614aa01184893f9f29f20a347e release-1.1.0
 | 
			
		||||
cfb1e028fd0627614aa01184893f9f29f20a347e release-1.1.0
 | 
			
		||||
0000000000000000000000000000000000000000 release-1.1.0
 | 
			
		||||
0000000000000000000000000000000000000000 release-1.1.0
 | 
			
		||||
98da2ebf0d50dffe8b625457a639bc2f15519714 release-1.1.0
 | 
			
		||||
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@@ -2,7 +2,7 @@
 | 
			
		||||
# Makefile for contented
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
VERSION:=1.2.0
 | 
			
		||||
VERSION:=1.2.2
 | 
			
		||||
 | 
			
		||||
SOURCES:=Makefile \
 | 
			
		||||
	static \
 | 
			
		||||
@@ -69,5 +69,5 @@ _dist/contented-$(VERSION)-win32.7z: build/win32/contented.exe
 | 
			
		||||
	)
 | 
			
		||||
	
 | 
			
		||||
_dist/contented-$(VERSION)-src.zip: $(SOURCES)
 | 
			
		||||
	 hg archive --type=zip _dist/contented-$(VERSION)-src.zip
 | 
			
		||||
	git archive HEAD -o _dist/contented-$(VERSION)-src.zip
 | 
			
		||||
	
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ func idToString(v uint64) string {
 | 
			
		||||
	hd := hashids.NewData()
 | 
			
		||||
	hd.Salt = hashIdSalt
 | 
			
		||||
	hd.MinLength = hashIdMinLength
 | 
			
		||||
	h, _ := hashids.NewWithData(hd)
 | 
			
		||||
	h := hashids.NewWithData(hd)
 | 
			
		||||
	s, _ := h.EncodeInt64([]int64{int64(v)})
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										128
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
# contented
 | 
			
		||||
 | 
			
		||||
[](doc/image1.png)
 | 
			
		||||
 | 
			
		||||
A file / image / paste upload server with a focus on embedding.
 | 
			
		||||
 | 
			
		||||
You can use contented as a standalone upload server, or you can use the SDK to embed its upload widget into another website.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
- Drag and drop upload
 | 
			
		||||
- Multiple files upload
 | 
			
		||||
- Pastebin upload
 | 
			
		||||
- Custom drawing upload ([via drawingboard.js](https://github.com/Leimi/drawingboard.js))
 | 
			
		||||
- Ctrl-V upload
 | 
			
		||||
- SDK-oriented design for embedding, including CORS support
 | 
			
		||||
- Mobile friendly HTML interface
 | 
			
		||||
- Preserves uploaded filename and content-type metadata
 | 
			
		||||
- Hash verification (SHA512/256)
 | 
			
		||||
- Detect duplicate upload content and reuse storage
 | 
			
		||||
- Options to limit the upload filesize and the upload bandwidth
 | 
			
		||||
- Short URLs (using [Hashids](http://hashids.org) algorithm)
 | 
			
		||||
- Image thumbnailing
 | 
			
		||||
 | 
			
		||||
## Usage (Server)
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Usage of contented:
 | 
			
		||||
  -data string
 | 
			
		||||
        Directory for stored content (default "")
 | 
			
		||||
  -db string
 | 
			
		||||
        Path for metadata database (default "contented.db")
 | 
			
		||||
  -diskFilesWorldReadable
 | 
			
		||||
        Save files as 0644 instead of 0600
 | 
			
		||||
  -enableHomepage
 | 
			
		||||
        Enable homepage (disable for embedded use only) (default true)
 | 
			
		||||
  -listen string
 | 
			
		||||
        IP/Port to bind server (default "127.0.0.1:80")
 | 
			
		||||
  -max int
 | 
			
		||||
        Maximum size of uploaded files in MiB (set zero for unlimited) (default 8)
 | 
			
		||||
  -speed int
 | 
			
		||||
        Maximum upload speed in bytes/sec (set zero for unlimited)
 | 
			
		||||
  -title string
 | 
			
		||||
        Title used in web interface (default "contented")
 | 
			
		||||
  -trustXForwardedFor
 | 
			
		||||
        Trust X-Forwarded-For reverse proxy headers
 | 
			
		||||
  -concurrentthumbs
 | 
			
		||||
        Simultaneous thumbnail generation (default 16)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
If you are hosting behind a reverse proxy, remember to set its post body size parameter appropriately (e.g. `client_max_body_size` for nginx).
 | 
			
		||||
 | 
			
		||||
## Usage (HTTP)
 | 
			
		||||
 | 
			
		||||
The server responds on the following URLs:
 | 
			
		||||
 | 
			
		||||
URL                  |Method |Description
 | 
			
		||||
---------------------|-------|---
 | 
			
		||||
`/get/{ID}`          |`GET`  |Download item content
 | 
			
		||||
`/info/{ID}`         |`GET`  |Get item content metadata (JSON)
 | 
			
		||||
`/thumb/{Type}/{ID}` |`GET`  |Get item thumbnail image
 | 
			
		||||
`/about`             |`GET`  |Get server metadata (JSON)
 | 
			
		||||
 | 
			
		||||
## Usage (Embedding for web)
 | 
			
		||||
 | 
			
		||||
Your webpage should load the SDK from the contented server, then call the `contented.init` function to display the upload widget over the top of an existing DOM element. Your callback will be passed an array of file IDs of any uploaded items.
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<script type="text/javascript" src="SERVER_ADDR/sdk.js"></script>
 | 
			
		||||
contented.init("#target", function(/* String[] */ items) {});
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Changelog
 | 
			
		||||
 | 
			
		||||
2020-07-25: 1.3.1
 | 
			
		||||
- Fix an issue with dependencies causing failure to compile in Modules mode
 | 
			
		||||
 | 
			
		||||
2020-07-25: 1.3.0
 | 
			
		||||
- Feature: Option to limit concurrent thumbnail generation
 | 
			
		||||
- Enhancement: Set charset=UTF-8 when serving user-submitted text/plain content
 | 
			
		||||
- Fix an issue with large memory usage for multipart file uploads
 | 
			
		||||
 | 
			
		||||
2018-06-09: 1.2.1
 | 
			
		||||
- Feature: Add OpenGraph tags on preview pages, for rich metadata in chat applications
 | 
			
		||||
- Update thumbnailing library to improve quality
 | 
			
		||||
- Use dep for vendoring
 | 
			
		||||
- [⬇️ contented-1.2.1-win32.7z](https://git.ivysaur.me/attachments/88dea4f7-e314-4325-a957-096dcf8cdecc) *(1.51 MiB)*
 | 
			
		||||
- [⬇️ contented-1.2.1-src.zip](https://git.ivysaur.me/attachments/6fd2b963-3be4-48a6-a5bf-6f273bcaea24) *(1.49 MiB)*
 | 
			
		||||
- [⬇️ contented-1.2.1-linux64.tar.gz](https://git.ivysaur.me/attachments/c536f764-0250-4d67-886a-4797946e1124) *(2.21 MiB)*
 | 
			
		||||
 | 
			
		||||
2017-11-18: 1.2.0
 | 
			
		||||
- Feature: Thumbnail support
 | 
			
		||||
- Feature: File preview page
 | 
			
		||||
- Feature: Album mode (via URL `/p/{file1}-{file2}-...`)
 | 
			
		||||
- Feature: New `-diskFilesWorldReadable` option to save files with `0644` mode
 | 
			
		||||
- [⬇️ contented-1.2.0-win32.7z](https://git.ivysaur.me/attachments/f3453b62-b2a7-4e77-9b04-44c99dec35ba) *(1.36 MiB)*
 | 
			
		||||
- [⬇️ contented-1.2.0-src.zip](https://git.ivysaur.me/attachments/a6c1ecfb-fd6a-44b5-9dc8-aea7c439d1e6) *(178.94 KiB)*
 | 
			
		||||
- [⬇️ contented-1.2.0-linux64.tar.gz](https://git.ivysaur.me/attachments/6234754b-af17-4a72-8b66-56a5db21c7c7) *(2.03 MiB)*
 | 
			
		||||
 | 
			
		||||
2017-10-15: 1.1.0
 | 
			
		||||
- Feature: Drawing mode
 | 
			
		||||
- Feature: Ctrl+V image upload
 | 
			
		||||
- Feature: Option to trust X-Forwarded-For headers when using a reverse proxy
 | 
			
		||||
- Feature: Add `getDownloadURL`, `getInfoJSONURL`, `getPreviewURL` SDK methods
 | 
			
		||||
- Feature: Option to disable uploading via the homepage
 | 
			
		||||
- Feature: Add button to repeat when uploading from homepage
 | 
			
		||||
- Enhancement: Automatically load library dependencies
 | 
			
		||||
- Enhancement: Display homepage widget using the full screen size
 | 
			
		||||
- Include drawingboard.js 0.4.6 (MIT license)
 | 
			
		||||
- Fix a cosmetic issue with javascript console output
 | 
			
		||||
- Fix a cosmetic issue with error messages if an upload failed
 | 
			
		||||
- [⬇️ contented-1.1.0-win32.7z](https://git.ivysaur.me/attachments/bfb0a7fe-bf95-4d0e-933b-8137bc8071a4) *(1.11 MiB)*
 | 
			
		||||
- [⬇️ contented-1.1.0-src.zip](https://git.ivysaur.me/attachments/67401341-724f-4ea2-b9c7-44d08ab9d38a) *(142.82 KiB)*
 | 
			
		||||
- [⬇️ contented-1.1.0-linux64.tar.gz](https://git.ivysaur.me/attachments/a13752dd-5228-4830-b61d-0f7cc568b2ae) *(1.67 MiB)*
 | 
			
		||||
 | 
			
		||||
2017-10-08: 1.0.1
 | 
			
		||||
- Fix an issue with CORS preflight requests
 | 
			
		||||
- Fix an issue with index URLs
 | 
			
		||||
- [⬇️ contented-1.0.1-win32.7z](https://git.ivysaur.me/attachments/a873d510-da09-4797-95e9-ffcad690a77b) *(1.10 MiB)*
 | 
			
		||||
- [⬇️ contented-1.0.1-src.zip](https://git.ivysaur.me/attachments/43ac17d6-b6f1-4da7-98e9-b8af6fb5551a) *(109.08 KiB)*
 | 
			
		||||
- [⬇️ contented-1.0.1-linux64.tar.gz](https://git.ivysaur.me/attachments/34d74bed-db3f-4cef-a76f-266f0b9e6017) *(1.65 MiB)*
 | 
			
		||||
 | 
			
		||||
2017-10-08: 1.0.0
 | 
			
		||||
- Initial public release
 | 
			
		||||
- Include jQuery 1.12.4 (MIT license)
 | 
			
		||||
- [⬇️ contented-1.0.0-win32.7z](https://git.ivysaur.me/attachments/4ef132cf-dac8-4bcf-9da7-14ca1366e815) *(1.10 MiB)*
 | 
			
		||||
- [⬇️ contented-1.0.0-src.zip](https://git.ivysaur.me/attachments/74d77b3f-557b-44bf-9645-7b3b25ab17c1) *(102.45 KiB)*
 | 
			
		||||
- [⬇️ contented-1.0.0-linux64.tar.gz](https://git.ivysaur.me/attachments/1c28a913-686b-44cf-b63d-db22968a93b6) *(1.65 MiB)*
 | 
			
		||||
							
								
								
									
										16
									
								
								Server.go
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								Server.go
									
									
									
									
									
								
							@@ -16,9 +16,12 @@ import (
 | 
			
		||||
 | 
			
		||||
var SERVER_HEADER string = `contented/0.0.0-dev`
 | 
			
		||||
 | 
			
		||||
const DEFAULT_MAX_CONCURRENT_THUMBS = 16
 | 
			
		||||
 | 
			
		||||
type ServerPublicProperties struct {
 | 
			
		||||
	AppTitle         string
 | 
			
		||||
	MaxUploadBytes   int64
 | 
			
		||||
	CanonicalBaseURL string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ServerOptions struct {
 | 
			
		||||
@@ -28,6 +31,7 @@ type ServerOptions struct {
 | 
			
		||||
	BandwidthLimit         int64
 | 
			
		||||
	TrustXForwardedFor     bool
 | 
			
		||||
	EnableHomepage         bool
 | 
			
		||||
	MaxConcurrentThumbs    int
 | 
			
		||||
	ServerPublicProperties
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -43,6 +47,7 @@ type Server struct {
 | 
			
		||||
	opts           ServerOptions
 | 
			
		||||
	db             *bolt.DB
 | 
			
		||||
	startTime      time.Time
 | 
			
		||||
	thumbnailSem   chan struct{}
 | 
			
		||||
	metadataBucket []byte
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -53,6 +58,17 @@ func NewServer(opts *ServerOptions) (*Server, error) {
 | 
			
		||||
		startTime:      time.Now(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if s.opts.MaxConcurrentThumbs <= 0 {
 | 
			
		||||
		s.opts.MaxConcurrentThumbs = DEFAULT_MAX_CONCURRENT_THUMBS // default
 | 
			
		||||
		log.Printf("Allowing %d concurrent thumbnails", s.opts.MaxConcurrentThumbs)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// "fill" the thumbnailer semaphore
 | 
			
		||||
	s.thumbnailSem = make(chan struct{}, s.opts.MaxConcurrentThumbs)
 | 
			
		||||
	for i := 0; i < s.opts.MaxConcurrentThumbs; i += 1 {
 | 
			
		||||
		s.thumbnailSem <- struct{}{}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	b, err := bolt.Open(opts.DBPath, 0644, bolt.DefaultOptions)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								TODO.txt
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								TODO.txt
									
									
									
									
									
								
							@@ -1,14 +0,0 @@
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
- View server-wide recent uploads history / all upload history
 | 
			
		||||
 | 
			
		||||
- Display 'my uploads' (id + metadata history kept in localstorage)
 | 
			
		||||
 | 
			
		||||
- Encrypted at rest (anti- provider snooping)
 | 
			
		||||
 | 
			
		||||
- Prevent selecting around the toggle buttons (firefox for android)
 | 
			
		||||
 | 
			
		||||
- Detect when pasting creates one large file and one ~150 byte file(?? why does this happen?)
 | 
			
		||||
 | 
			
		||||
- Detect when pasting an image URL, offer to download it (clientside?)
 | 
			
		||||
 | 
			
		||||
@@ -1,92 +0,0 @@
 | 
			
		||||
A file / image / paste upload server with a focus on embedding.
 | 
			
		||||
 | 
			
		||||
Written in Go
 | 
			
		||||
 | 
			
		||||
You can use contented as a standalone upload server, or you can use the SDK to embed its upload widget into another website.
 | 
			
		||||
 | 
			
		||||
=FEATURES=
 | 
			
		||||
 | 
			
		||||
- Drag and drop upload
 | 
			
		||||
- Multiple files upload
 | 
			
		||||
- Pastebin upload
 | 
			
		||||
- Custom drawing upload ([url=https://github.com/Leimi/drawingboard.js]via[/url])
 | 
			
		||||
- Ctrl-V upload
 | 
			
		||||
- SDK-oriented design for embedding, including CORS support
 | 
			
		||||
- Mobile friendly HTML interface
 | 
			
		||||
- Preserves uploaded filename and content-type metadata
 | 
			
		||||
- Hash verification (SHA512/256)
 | 
			
		||||
- Detect duplicate upload content and reuse storage
 | 
			
		||||
- Options to limit the upload filesize and the upload bandwidth
 | 
			
		||||
- Short URLs (using [url=http://hashids.org]Hashids[/url] algorithm)
 | 
			
		||||
- Image thumbnailing
 | 
			
		||||
 | 
			
		||||
=USAGE (SERVER)=
 | 
			
		||||
 | 
			
		||||
`Usage of contented:
 | 
			
		||||
  -data string
 | 
			
		||||
        Directory for stored content (default "")
 | 
			
		||||
  -db string
 | 
			
		||||
        Path for metadata database (default "contented.db")
 | 
			
		||||
  -diskFilesWorldReadable
 | 
			
		||||
        Save files as 0644 instead of 0600
 | 
			
		||||
  -enableHomepage
 | 
			
		||||
        Enable homepage (disable for embedded use only) (default true)
 | 
			
		||||
  -listen string
 | 
			
		||||
        IP/Port to bind server (default "127.0.0.1:80")
 | 
			
		||||
  -max int
 | 
			
		||||
        Maximum size of uploaded files in MiB (set zero for unlimited) (default 8)
 | 
			
		||||
  -speed int
 | 
			
		||||
        Maximum upload speed in bytes/sec (set zero for unlimited)
 | 
			
		||||
  -title string
 | 
			
		||||
        Title used in web interface (default "contented")
 | 
			
		||||
  -trustXForwardedFor
 | 
			
		||||
        Trust X-Forwarded-For reverse proxy headers
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
If you are hosting behind a reverse proxy, remember to set its post body size parameter appropriately (e.g. `client_max_body_size` for nginx).
 | 
			
		||||
 | 
			
		||||
=USAGE (HTTP)=
 | 
			
		||||
 | 
			
		||||
The server responds on the following URLs:
 | 
			
		||||
 | 
			
		||||
- `/get/{ID}`: Download item content
 | 
			
		||||
- `/info/{ID}`: Get item content metadata (JSON)
 | 
			
		||||
- `/thumb/{Type}/{ID}`: Get item thumbnail image
 | 
			
		||||
- `/about`: Get server metadata (JSON)
 | 
			
		||||
 | 
			
		||||
=USAGE (EMBEDDING FOR WEB)=
 | 
			
		||||
 | 
			
		||||
Your webpage should load the SDK from the contented server, then call the `contented.init` function to display the upload widget over the top of an existing DOM element. Your callback will be passed an array of file IDs of any uploaded items.
 | 
			
		||||
 | 
			
		||||
`<script type="text/javascript" src="SERVER_ADDR/sdk.js"></script>
 | 
			
		||||
contented.init("#target", function(/* String[] */ items) {});
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
=CHANGELOG=
 | 
			
		||||
 | 
			
		||||
2017-11-18: 1.2.0
 | 
			
		||||
- Feature: Thumbnail support
 | 
			
		||||
- Feature: File preview page
 | 
			
		||||
- Feature: Album mode (via URL `/p/{file1}-{file2}-...`)
 | 
			
		||||
- Feature: New `-diskFilesWorldReadable` option to save files with `0644` mode
 | 
			
		||||
 | 
			
		||||
2017-10-15: 1.1.0
 | 
			
		||||
- Feature: Drawing mode
 | 
			
		||||
- Feature: Ctrl+V image upload
 | 
			
		||||
- Feature: Option to trust X-Forwarded-For headers when using a reverse proxy
 | 
			
		||||
- Feature: Add `getDownloadURL`, `getInfoJSONURL`, `getPreviewURL` SDK methods
 | 
			
		||||
- Feature: Option to disable uploading via the homepage
 | 
			
		||||
- Feature: Add button to repeat when uploading from homepage
 | 
			
		||||
- Enhancement: Automatically load library dependencies
 | 
			
		||||
- Enhancement: Display homepage widget using the full screen size
 | 
			
		||||
- Include drawingboard.js 0.4.6 (MIT license)
 | 
			
		||||
- Fix a cosmetic issue with javascript console output
 | 
			
		||||
- Fix a cosmetic issue with error messages if an upload failed
 | 
			
		||||
 | 
			
		||||
2017-10-08: 1.0.1
 | 
			
		||||
- Fix an issue with CORS preflight requests
 | 
			
		||||
- Fix an issue with index URLs
 | 
			
		||||
 | 
			
		||||
2017-10-08: 1.0.0
 | 
			
		||||
- Initial public release
 | 
			
		||||
- Include jQuery 1.12.4 (MIT license)
 | 
			
		||||
@@ -21,6 +21,7 @@ func main() {
 | 
			
		||||
	trustXForwardedFor := flag.Bool("trustXForwardedFor", false, "Trust X-Forwarded-For reverse proxy headers")
 | 
			
		||||
	enableHomepage := flag.Bool("enableHomepage", true, "Enable homepage (disable for embedded use only)")
 | 
			
		||||
	diskFilesWorldReadable := flag.Bool("diskFilesWorldReadable", false, "Save files as 0644 instead of 0600")
 | 
			
		||||
	maxConcurrentThumbs := flag.Int("concurrentthumbs", contented.DEFAULT_MAX_CONCURRENT_THUMBS, "Simultaneous thumbnail generation")
 | 
			
		||||
 | 
			
		||||
	flag.Parse()
 | 
			
		||||
 | 
			
		||||
@@ -31,6 +32,7 @@ func main() {
 | 
			
		||||
		TrustXForwardedFor:     *trustXForwardedFor,
 | 
			
		||||
		EnableHomepage:         *enableHomepage,
 | 
			
		||||
		DiskFilesWorldReadable: *diskFilesWorldReadable,
 | 
			
		||||
		MaxConcurrentThumbs:    *maxConcurrentThumbs,
 | 
			
		||||
		ServerPublicProperties: contented.ServerPublicProperties{
 | 
			
		||||
			AppTitle:       *appTitle,
 | 
			
		||||
			MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024,
 | 
			
		||||
 
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								doc/image1.thumb.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								doc/image1.thumb.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 5.3 KiB  | 
							
								
								
									
										10
									
								
								download.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								download.go
									
									
									
									
									
								
							@@ -37,9 +37,17 @@ func (this *Server) handleViewInternal(w http.ResponseWriter, r *http.Request, f
 | 
			
		||||
 | 
			
		||||
	// ServeContent only uses the filename to get the mime type, which we can
 | 
			
		||||
	// set accurately (including blacklist)
 | 
			
		||||
 | 
			
		||||
	switch m.MimeType {
 | 
			
		||||
	case `text/plain`:
 | 
			
		||||
		w.Header().Set(`Content-Type`, `text/plain; charset=UTF-8`)
 | 
			
		||||
 | 
			
		||||
	case `application/octet-stream`:
 | 
			
		||||
		w.Header().Set(`Content-Type`, m.MimeType)
 | 
			
		||||
	if m.MimeType == `application/octet-stream` {
 | 
			
		||||
		w.Header().Set(`Content-Disposition`, `attachment; filename="`+m.Filename+`"`)
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
		w.Header().Set(`Content-Type`, m.MimeType)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	http.ServeContent(w, r, "", m.UploadTime, f)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
module code.ivysaur.me/contented
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	code.ivysaur.me/thumbnail v1.0.2
 | 
			
		||||
	github.com/boltdb/bolt v1.3.1
 | 
			
		||||
	github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
 | 
			
		||||
	github.com/speps/go-hashids v1.0.0
 | 
			
		||||
	golang.org/x/sys v0.0.0-20180606202747-9527bec2660b // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
go 1.13
 | 
			
		||||
							
								
								
									
										17
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
code.ivysaur.me/imagequant/v2 v2.12.6 h1:xYrGj6GOdAcutmzqBxG7bDZ70r4jYHADOCZ+ktyMU3Y=
 | 
			
		||||
code.ivysaur.me/imagequant/v2 v2.12.6/go.mod h1:seCAm0sP2IBsb1YNBj4D+EZovIuGe16+6Xo0aiGyhDU=
 | 
			
		||||
code.ivysaur.me/thumbnail v1.0.2 h1:vQaRPbBZOUGpr4b5rrUOHiZv08XSRJ83uu64WXFx7mo=
 | 
			
		||||
code.ivysaur.me/thumbnail v1.0.2/go.mod h1:sXeHBfmPfiSe5ZBKsbGSES13C9OSZq0WmT4yZ/XBeeE=
 | 
			
		||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
 | 
			
		||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 | 
			
		||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 | 
			
		||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
 | 
			
		||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
 | 
			
		||||
github.com/speps/go-hashids v1.0.0 h1:jdFC07PrExRM4Og5Ev4411Tox75aFpkC77NlmutadNI=
 | 
			
		||||
github.com/speps/go-hashids v1.0.0/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc=
 | 
			
		||||
golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
 | 
			
		||||
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b h1:5rOiLYVqtE+JehJPVJTXQJaP8aT3cpJC1Iy22+5WLFU=
 | 
			
		||||
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
							
								
								
									
										25
									
								
								preview.go
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								preview.go
									
									
									
									
									
								
							@@ -15,10 +15,31 @@ func (this *Server) handlePreview(w http.ResponseWriter, fileIDList string) {
 | 
			
		||||
	fileIDs := strings.Split(fileIDList, `-`)
 | 
			
		||||
 | 
			
		||||
	tmpl := `<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
<html prefix="og: http://ogp.me/ns#">
 | 
			
		||||
	<head>
 | 
			
		||||
		<title>` + html.EscapeString(this.opts.ServerPublicProperties.AppTitle) + `</title>
 | 
			
		||||
		<meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
		<meta name="viewport" content="width=device-width, initial-scale=1" />
 | 
			
		||||
		<meta property="og:title" content="` + html.EscapeString(this.opts.ServerPublicProperties.AppTitle) + `" />
 | 
			
		||||
		<meta property="og:site_name" content="` + html.EscapeString(this.opts.ServerPublicProperties.AppTitle) + `" />
 | 
			
		||||
		<meta property="og:type" content="website" />
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
	if len(this.opts.ServerPublicProperties.CanonicalBaseURL) > 0 {
 | 
			
		||||
		tmpl += `
 | 
			
		||||
		<meta property="og:url" content="` + html.EscapeString(this.opts.ServerPublicProperties.CanonicalBaseURL+`p/`+fileIDList) + `" />
 | 
			
		||||
		`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, fileID := range fileIDs {
 | 
			
		||||
		tmpl += `
 | 
			
		||||
		<meta property="og:image" content="` + html.EscapeString(`/thumb/m/`+fileID) + `" />
 | 
			
		||||
		<meta property="og:image:type" content="image/jpeg" />
 | 
			
		||||
		<meta property="og:image:width" content="300" />
 | 
			
		||||
		<meta property="og:image:height" content="300" />
 | 
			
		||||
		`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tmpl += `
 | 
			
		||||
		<style type="text/css">
 | 
			
		||||
html, body {
 | 
			
		||||
	background: #333;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								thumb.go
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								thumb.go
									
									
									
									
									
								
							@@ -1,6 +1,7 @@
 | 
			
		||||
package contented
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
@@ -10,52 +11,77 @@ import (
 | 
			
		||||
	"code.ivysaur.me/thumbnail"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func thumbnailer(t byte) (*thumbnail.Thumbnailer, error) {
 | 
			
		||||
func getThumbnailerConfig(t byte) (*thumbnail.Config, error) {
 | 
			
		||||
	// Modelled on what imgur.com offers
 | 
			
		||||
	// @ref https://api.imgur.com/models/image#thumbs
 | 
			
		||||
 | 
			
		||||
	const (
 | 
			
		||||
		cacheSize = 1
 | 
			
		||||
		outputFmt = thumbnail.OUTPUT_JPG
 | 
			
		||||
		scaleFmt  = thumbnail.SCALEFMT_BILINEAR
 | 
			
		||||
	)
 | 
			
		||||
	opts := thumbnail.Config{
 | 
			
		||||
		Aspect: thumbnail.FitOutside,
 | 
			
		||||
		Output: thumbnail.Jpeg,
 | 
			
		||||
		Scale:  thumbnail.Bicubic,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch t {
 | 
			
		||||
	case 's':
 | 
			
		||||
		return thumbnail.NewThumbnailerEx(90, 90, cacheSize, outputFmt, thumbnail.ASPECT_CROP_TO_DIMENSIONS, scaleFmt), nil
 | 
			
		||||
		opts.Width = 90
 | 
			
		||||
		opts.Height = 90
 | 
			
		||||
	case 'b':
 | 
			
		||||
		return thumbnail.NewThumbnailerEx(160, 160, cacheSize, outputFmt, thumbnail.ASPECT_CROP_TO_DIMENSIONS, scaleFmt), nil
 | 
			
		||||
		opts.Width = 160
 | 
			
		||||
		opts.Height = 160
 | 
			
		||||
	case 't':
 | 
			
		||||
		return thumbnail.NewThumbnailerEx(160, 160, cacheSize, outputFmt, thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY, scaleFmt), nil
 | 
			
		||||
		opts.Width = 160
 | 
			
		||||
		opts.Height = 160
 | 
			
		||||
		// thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY
 | 
			
		||||
	case 'm':
 | 
			
		||||
		return thumbnail.NewThumbnailerEx(340, 340, cacheSize, outputFmt, thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY, scaleFmt), nil
 | 
			
		||||
		opts.Width = 340
 | 
			
		||||
		opts.Height = 340
 | 
			
		||||
		// thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY
 | 
			
		||||
	case 'l':
 | 
			
		||||
		return thumbnail.NewThumbnailerEx(640, 640, cacheSize, outputFmt, thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY, scaleFmt), nil
 | 
			
		||||
		opts.Width = 640
 | 
			
		||||
		opts.Height = 640
 | 
			
		||||
		// thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY
 | 
			
		||||
	case 'h':
 | 
			
		||||
		return thumbnail.NewThumbnailerEx(1024, 1024, cacheSize, outputFmt, thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY, scaleFmt), nil
 | 
			
		||||
		opts.Width = 1024
 | 
			
		||||
		opts.Height = 1024
 | 
			
		||||
		// thumbnail.ASPECT_RESPECT_MAX_DIMENSION_ONLY
 | 
			
		||||
	default:
 | 
			
		||||
		return nil, errors.New("Unsupported thumbnail type (should be s/b/t/m/l/h)")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &opts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Server) handleThumb(w http.ResponseWriter, r *http.Request, thumbnailType byte, fileId string) {
 | 
			
		||||
	t, err := thumbnailer(thumbnailType)
 | 
			
		||||
	ctx := r.Context()
 | 
			
		||||
 | 
			
		||||
	opts, err := getThumbnailerConfig(thumbnailType)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("%s Thumbnail failed: %s\n", this.remoteIP(r), err.Error())
 | 
			
		||||
		http.Error(w, err.Error(), 400)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = this.handleThumbInternal(w, t, fileId)
 | 
			
		||||
	// Only a limited number of thumbnails can be generated concurrently
 | 
			
		||||
	<-this.thumbnailSem
 | 
			
		||||
	defer func() { this.thumbnailSem <- struct{}{} }()
 | 
			
		||||
 | 
			
		||||
	if ctx.Err() != nil {
 | 
			
		||||
		// The request was already cancelled
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t := thumbnail.NewThumbnailerEx(opts)
 | 
			
		||||
 | 
			
		||||
	err = this.handleThumbInternal(ctx, w, t, fileId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("%s Thumbnail failed: %s\n", this.remoteIP(r), err.Error())
 | 
			
		||||
 | 
			
		||||
		w.Header().Set(`Location`, fmt.Sprintf(`/nothumb_%d.png`, t.Height()))
 | 
			
		||||
		w.Header().Set(`Location`, fmt.Sprintf(`/nothumb_%d.png`, opts.Height))
 | 
			
		||||
		w.WriteHeader(302)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Server) handleThumbInternal(w http.ResponseWriter, t *thumbnail.Thumbnailer, fileId string) error {
 | 
			
		||||
func (this *Server) handleThumbInternal(ctx context.Context, w http.ResponseWriter, t thumbnail.Thumbnailer, fileId string) error {
 | 
			
		||||
 | 
			
		||||
	// Load metadata
 | 
			
		||||
	m, err := this.Metadata(fileId)
 | 
			
		||||
@@ -64,7 +90,7 @@ func (this *Server) handleThumbInternal(w http.ResponseWriter, t *thumbnail.Thum
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	filePath := filepath.Join(this.opts.DataDirectory, m.FileHash)
 | 
			
		||||
	thumb, err := t.RenderFile_NoCache_MimeType(filePath, m.MimeType)
 | 
			
		||||
	thumb, err := t.RenderFileAs(filePath, m.MimeType)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ func (this *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
 | 
			
		||||
	remoteIP := this.remoteIP(r)
 | 
			
		||||
 | 
			
		||||
	err := r.ParseMultipartForm(this.opts.MaxUploadBytes * 2)
 | 
			
		||||
	err := r.ParseMultipartForm(0) // buffer upload in temporary files on disk, not memory
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Printf("%s Invalid request: %s\n", remoteIP, err.Error())
 | 
			
		||||
		http.Error(w, "Invalid request", 400)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user