37 Commits

Author SHA1 Message Date
ccc1f73e52 doc: update README for 1.3.1 2020-07-25 13:17:02 +12:00
b48d1347c7 vendor: update package versions (fixes FTBFS) 2020-07-25 13:15:50 +12:00
bed8a88b09 vendor: delete vendor directory, rely fully on Go modules 2020-07-25 13:14:44 +12:00
0a70d99af5 changelog for 1.3.0 2020-07-25 12:35:26 +12:00
6720cbc0d9 upload: buffer MIME parsing on disk, not in memory 2020-07-25 12:25:44 +12:00
8d11b7c434 doc/README: add links for archived releases 2020-05-06 18:05:58 +12:00
147608a327 hg2git: fix source tarball generation in Makefile 2018-12-31 19:02:58 +13:00
951c87b63f convert to Go Modules 2018-12-31 19:02:50 +13:00
25180afaa2 doc: fix markdown syntax 2018-10-06 14:04:13 +13:00
33ca03b9d7 doc: update README for GFM syntax 2018-10-06 13:49:20 +13:00
b562ca4bd4 doc: move marketing images to /doc/ subdir 2018-10-06 13:49:00 +13:00
a25dc5827b doc: add image1.png thumbnail 2018-10-06 13:43:02 +13:00
5263f96956 doc: update readme 2018-09-09 19:03:29 +12:00
2b2add62b2 vcs: remove old hgtags 2018-09-09 18:45:14 +12:00
fdc93e6485 vcs: migrate hgignore->gitignore 2018-09-09 18:45:07 +12:00
b08f1c33d5 thumbs: allow configuring limit on simultanous thumbs (default 16) 2018-09-09 18:41:37 +12:00
524f37d9fe serve text/plain content with charset=utf-8 header 2018-09-09 18:33:33 +12:00
c958c57794 thumb: only generate one thumbnail concurrently 2018-09-09 18:31:25 +12:00
8fbad2a1e0 doc: move README to top-level, for web viewing 2018-07-21 13:38:46 +12:00
989cd195f8 doc: remove TODO file
Issues are now tracked at git.ivysaur.me.
2018-07-21 13:36:14 +12:00
cb4933fa72 doc: update TODO 2018-07-10 17:45:58 +12:00
f504ab5929 bump version to 1.2.2 2018-06-09 18:13:22 +12:00
f8e95a8037 add go-get tags to readme 2018-06-09 18:12:50 +12:00
feaa51cfcd Added tag v1.2.1 for changeset 7c3807929e7a 2018-06-09 18:11:39 +12:00
ddf76aacc5 doc: update readme 2018-06-09 18:10:54 +12:00
ca8a0d55ba compatibility fixes for hashids library 2018-06-09 18:10:49 +12:00
fe4aace777 thumb: compatibility fixes for thumbnail library 2018-06-09 18:09:57 +12:00
be864235cb vendor: update thumbnail from 1.0.0 -> master 2018-06-09 18:09:49 +12:00
dba699524f build: remove disableimagecrush tag, as it's now the default 2018-06-09 17:55:25 +12:00
7950ca3004 use dep to manage vendor directory 2018-06-09 17:54:49 +12:00
6e5ca0c61c retag releases to semver format 2018-06-04 18:30:42 +12:00
7255cd03cb makefile: build without imagecrush support 2018-06-04 18:28:58 +12:00
3cf4986418 fix typo in previous 2018-06-04 18:28:50 +12:00
0a338c4568 add OpenGraph meta tags, for image preview inside chat apps like Telegram 2018-06-04 17:22:28 +12:00
0fbf401fd3 doc: update TODO 2018-03-01 18:38:42 +13:00
48ca68fafd Added tag release-1.2.0 for changeset 0f021da52854 2017-11-18 14:30:26 +13:00
f3e594d307 bump all versions to 1.2.1 2017-11-18 14:30:21 +13:00
17 changed files with 256 additions and 146 deletions

View File

@@ -1,5 +1,3 @@
syntax: glob
cmd/contented/contented cmd/contented/contented
build/ build/
_dist/ _dist/

11
.hgtags
View File

@@ -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

View File

@@ -2,7 +2,7 @@
# Makefile for contented # Makefile for contented
# #
VERSION:=1.2.0 VERSION:=1.2.2
SOURCES:=Makefile \ SOURCES:=Makefile \
static \ static \
@@ -69,5 +69,5 @@ _dist/contented-$(VERSION)-win32.7z: build/win32/contented.exe
) )
_dist/contented-$(VERSION)-src.zip: $(SOURCES) _dist/contented-$(VERSION)-src.zip: $(SOURCES)
hg archive --type=zip _dist/contented-$(VERSION)-src.zip git archive HEAD -o _dist/contented-$(VERSION)-src.zip

View File

@@ -47,7 +47,7 @@ func idToString(v uint64) string {
hd := hashids.NewData() hd := hashids.NewData()
hd.Salt = hashIdSalt hd.Salt = hashIdSalt
hd.MinLength = hashIdMinLength hd.MinLength = hashIdMinLength
h, _ := hashids.NewWithData(hd) h := hashids.NewWithData(hd)
s, _ := h.EncodeInt64([]int64{int64(v)}) s, _ := h.EncodeInt64([]int64{int64(v)})
return s return s
} }

128
README.md Normal file
View File

@@ -0,0 +1,128 @@
# contented
[![](doc/image1.thumb.png)](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)*

View File

@@ -16,9 +16,12 @@ import (
var SERVER_HEADER string = `contented/0.0.0-dev` var SERVER_HEADER string = `contented/0.0.0-dev`
const DEFAULT_MAX_CONCURRENT_THUMBS = 16
type ServerPublicProperties struct { type ServerPublicProperties struct {
AppTitle string AppTitle string
MaxUploadBytes int64 MaxUploadBytes int64
CanonicalBaseURL string
} }
type ServerOptions struct { type ServerOptions struct {
@@ -28,6 +31,7 @@ type ServerOptions struct {
BandwidthLimit int64 BandwidthLimit int64
TrustXForwardedFor bool TrustXForwardedFor bool
EnableHomepage bool EnableHomepage bool
MaxConcurrentThumbs int
ServerPublicProperties ServerPublicProperties
} }
@@ -43,6 +47,7 @@ type Server struct {
opts ServerOptions opts ServerOptions
db *bolt.DB db *bolt.DB
startTime time.Time startTime time.Time
thumbnailSem chan struct{}
metadataBucket []byte metadataBucket []byte
} }
@@ -53,6 +58,17 @@ func NewServer(opts *ServerOptions) (*Server, error) {
startTime: time.Now(), 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) b, err := bolt.Open(opts.DBPath, 0644, bolt.DefaultOptions)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -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?)

View File

@@ -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)

View File

@@ -21,6 +21,7 @@ func main() {
trustXForwardedFor := flag.Bool("trustXForwardedFor", false, "Trust X-Forwarded-For reverse proxy headers") trustXForwardedFor := flag.Bool("trustXForwardedFor", false, "Trust X-Forwarded-For reverse proxy headers")
enableHomepage := flag.Bool("enableHomepage", true, "Enable homepage (disable for embedded use only)") enableHomepage := flag.Bool("enableHomepage", true, "Enable homepage (disable for embedded use only)")
diskFilesWorldReadable := flag.Bool("diskFilesWorldReadable", false, "Save files as 0644 instead of 0600") 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() flag.Parse()
@@ -31,6 +32,7 @@ func main() {
TrustXForwardedFor: *trustXForwardedFor, TrustXForwardedFor: *trustXForwardedFor,
EnableHomepage: *enableHomepage, EnableHomepage: *enableHomepage,
DiskFilesWorldReadable: *diskFilesWorldReadable, DiskFilesWorldReadable: *diskFilesWorldReadable,
MaxConcurrentThumbs: *maxConcurrentThumbs,
ServerPublicProperties: contented.ServerPublicProperties{ ServerPublicProperties: contented.ServerPublicProperties{
AppTitle: *appTitle, AppTitle: *appTitle,
MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024, MaxUploadBytes: int64(*maxUploadMb) * 1024 * 1024,

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
doc/image1.thumb.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -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 // ServeContent only uses the filename to get the mime type, which we can
// set accurately (including blacklist) // 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) w.Header().Set(`Content-Type`, m.MimeType)
if m.MimeType == `application/octet-stream` {
w.Header().Set(`Content-Disposition`, `attachment; filename="`+m.Filename+`"`) w.Header().Set(`Content-Disposition`, `attachment; filename="`+m.Filename+`"`)
default:
w.Header().Set(`Content-Type`, m.MimeType)
} }
http.ServeContent(w, r, "", m.UploadTime, f) http.ServeContent(w, r, "", m.UploadTime, f)

11
go.mod Normal file
View 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
View 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=

View File

@@ -15,10 +15,31 @@ func (this *Server) handlePreview(w http.ResponseWriter, fileIDList string) {
fileIDs := strings.Split(fileIDList, `-`) fileIDs := strings.Split(fileIDList, `-`)
tmpl := `<!DOCTYPE html> tmpl := `<!DOCTYPE html>
<html> <html prefix="og: http://ogp.me/ns#">
<head> <head>
<title>` + html.EscapeString(this.opts.ServerPublicProperties.AppTitle) + `</title> <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"> <style type="text/css">
html, body { html, body {
background: #333; background: #333;

View File

@@ -1,6 +1,7 @@
package contented package contented
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@@ -10,52 +11,77 @@ import (
"code.ivysaur.me/thumbnail" "code.ivysaur.me/thumbnail"
) )
func thumbnailer(t byte) (*thumbnail.Thumbnailer, error) { func getThumbnailerConfig(t byte) (*thumbnail.Config, error) {
// Modelled on what imgur.com offers // Modelled on what imgur.com offers
// @ref https://api.imgur.com/models/image#thumbs // @ref https://api.imgur.com/models/image#thumbs
const ( opts := thumbnail.Config{
cacheSize = 1 Aspect: thumbnail.FitOutside,
outputFmt = thumbnail.OUTPUT_JPG Output: thumbnail.Jpeg,
scaleFmt = thumbnail.SCALEFMT_BILINEAR Scale: thumbnail.Bicubic,
) }
switch t { switch t {
case 's': case 's':
return thumbnail.NewThumbnailerEx(90, 90, cacheSize, outputFmt, thumbnail.ASPECT_CROP_TO_DIMENSIONS, scaleFmt), nil opts.Width = 90
opts.Height = 90
case 'b': case 'b':
return thumbnail.NewThumbnailerEx(160, 160, cacheSize, outputFmt, thumbnail.ASPECT_CROP_TO_DIMENSIONS, scaleFmt), nil opts.Width = 160
opts.Height = 160
case 't': 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': 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': 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': 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: default:
return nil, errors.New("Unsupported thumbnail type (should be s/b/t/m/l/h)") 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) { 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 { if err != nil {
log.Printf("%s Thumbnail failed: %s\n", this.remoteIP(r), err.Error()) log.Printf("%s Thumbnail failed: %s\n", this.remoteIP(r), err.Error())
http.Error(w, err.Error(), 400) http.Error(w, err.Error(), 400)
return 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 { if err != nil {
log.Printf("%s Thumbnail failed: %s\n", this.remoteIP(r), err.Error()) 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) 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 // Load metadata
m, err := this.Metadata(fileId) 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) 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 { if err != nil {
return err return err
} }

View File

@@ -20,7 +20,7 @@ func (this *Server) handleUpload(w http.ResponseWriter, r *http.Request) {
remoteIP := this.remoteIP(r) 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 { if err != nil {
log.Printf("%s Invalid request: %s\n", remoteIP, err.Error()) log.Printf("%s Invalid request: %s\n", remoteIP, err.Error())
http.Error(w, "Invalid request", 400) http.Error(w, "Invalid request", 400)