2015-09-24 07:44:49 +02:00
package main
import (
"flag"
"log"
"net"
"net/http"
2015-10-01 16:32:59 +02:00
"net/http/fcgi"
2015-10-30 23:36:47 +01:00
"net/url"
2015-09-26 04:03:14 +02:00
"os"
2019-12-03 03:24:11 +01:00
"os/signal"
2015-09-28 04:17:12 +02:00
"regexp"
2015-10-01 16:32:59 +02:00
"strconv"
2016-06-04 07:49:01 +02:00
"strings"
2019-12-03 03:24:11 +01:00
"syscall"
2015-09-30 21:54:30 +02:00
"time"
2015-09-24 07:44:49 +02:00
2020-02-17 15:58:56 +01:00
rice "github.com/GeertJohan/go.rice"
2016-06-07 08:37:42 +02:00
"github.com/andreimarcu/linx-server/backends"
"github.com/andreimarcu/linx-server/backends/localfs"
2019-01-25 08:33:11 +01:00
"github.com/andreimarcu/linx-server/backends/s3"
2020-05-14 02:37:33 +02:00
"github.com/andreimarcu/linx-server/cleanup"
2015-09-25 05:21:37 +02:00
"github.com/flosch/pongo2"
2015-10-25 19:04:38 +01:00
"github.com/vharitonsky/iniflags"
2015-10-07 18:48:44 +02:00
"github.com/zenazn/goji/graceful"
2015-10-10 08:04:08 +02:00
"github.com/zenazn/goji/web"
2015-09-25 18:00:14 +02:00
"github.com/zenazn/goji/web/middleware"
2015-09-24 07:44:49 +02:00
)
2016-06-04 07:49:01 +02:00
type headerList [ ] string
func ( h * headerList ) String ( ) string {
return strings . Join ( * h , "," )
}
func ( h * headerList ) Set ( value string ) error {
* h = append ( * h , value )
return nil
}
2015-09-24 07:44:49 +02:00
var Config struct {
2015-10-04 23:58:00 +02:00
bind string
filesDir string
metaDir string
siteName string
siteURL string
2015-10-30 23:36:47 +01:00
sitePath string
2019-01-14 23:51:02 +01:00
selifPath string
2015-10-07 18:48:44 +02:00
certFile string
keyFile string
2015-10-04 23:58:00 +02:00
contentSecurityPolicy string
fileContentSecurityPolicy string
2019-01-08 20:56:09 +01:00
referrerPolicy string
fileReferrerPolicy string
2015-10-04 23:58:00 +02:00
xFrameOptions string
2015-10-08 07:38:50 +02:00
maxSize int64
2016-09-19 06:45:00 +02:00
maxExpiry uint64
2015-10-12 02:32:28 +02:00
realIp bool
2015-10-06 08:51:49 +02:00
noLogs bool
allowHotlink bool
fastcgi bool
remoteUploads bool
2020-03-07 00:21:49 +01:00
basicAuth bool
2015-10-10 21:26:47 +02:00
authFile string
2015-10-12 04:31:13 +02:00
remoteAuthFile string
2016-06-04 07:49:01 +02:00
addHeaders headerList
2018-11-07 19:13:27 +01:00
noDirectAgents bool
2019-01-25 08:33:11 +01:00
s3Endpoint string
s3Region string
s3Bucket string
2019-01-25 09:10:06 +01:00
s3ForcePathStyle bool
2019-01-26 11:04:32 +01:00
forceRandomFilename bool
2020-02-17 15:58:56 +01:00
accessKeyCookieExpiry uint64
2020-03-12 21:32:35 +01:00
customPagesDir string
2020-05-14 02:37:33 +02:00
cleanupEveryMinutes uint64
2015-09-24 07:44:49 +02:00
}
2015-09-29 02:46:58 +02:00
var Templates = make ( map [ string ] * pongo2 . Template )
var TemplateSet * pongo2 . TemplateSet
2015-09-30 21:54:30 +02:00
var staticBox * rice . Box
var timeStarted time . Time
2015-10-01 16:32:59 +02:00
var timeStartedStr string
2015-10-12 04:31:13 +02:00
var remoteAuthKeys [ ] string
2017-05-02 06:25:56 +02:00
var metaStorageBackend backends . MetaStorageBackend
2019-01-25 08:33:11 +01:00
var storageBackend backends . StorageBackend
2020-03-12 21:32:35 +01:00
var customPages = make ( map [ string ] string )
var customPagesNames = make ( map [ string ] string )
2015-09-29 02:46:58 +02:00
2015-10-10 08:04:08 +02:00
func setup ( ) * web . Mux {
mux := web . New ( )
// middleware
mux . Use ( middleware . RequestID )
2015-10-12 02:32:28 +02:00
if Config . realIp {
2015-10-10 17:22:24 +02:00
mux . Use ( middleware . RealIP )
}
2015-10-10 08:04:08 +02:00
if ! Config . noLogs {
mux . Use ( middleware . Logger )
}
mux . Use ( middleware . Recoverer )
mux . Use ( middleware . AutomaticOptions )
mux . Use ( ContentSecurityPolicy ( CSPOptions {
2019-01-08 20:56:09 +01:00
policy : Config . contentSecurityPolicy ,
referrerPolicy : Config . referrerPolicy ,
frame : Config . xFrameOptions ,
2015-10-04 23:58:00 +02:00
} ) )
2016-06-04 07:49:01 +02:00
mux . Use ( AddHeaders ( Config . addHeaders ) )
2015-10-04 23:58:00 +02:00
2015-10-10 21:26:47 +02:00
if Config . authFile != "" {
mux . Use ( UploadAuth ( AuthOptions {
AuthFile : Config . authFile ,
UnauthMethods : [ ] string { "GET" , "HEAD" , "OPTIONS" , "TRACE" } ,
} ) )
}
2015-09-28 04:17:12 +02:00
// make directories if needed
2015-09-29 02:46:58 +02:00
err := os . MkdirAll ( Config . filesDir , 0755 )
2015-09-26 04:03:14 +02:00
if err != nil {
2015-10-06 08:49:57 +02:00
log . Fatal ( "Could not create files directory:" , err )
2015-09-26 04:03:14 +02:00
}
2015-09-25 18:47:55 +02:00
2015-09-28 04:17:12 +02:00
err = os . MkdirAll ( Config . metaDir , 0700 )
if err != nil {
2015-10-06 08:49:57 +02:00
log . Fatal ( "Could not create metadata directory:" , err )
2015-09-28 04:17:12 +02:00
}
2016-06-04 10:22:01 +02:00
if Config . siteURL != "" {
// ensure siteURL ends wth '/'
if lastChar := Config . siteURL [ len ( Config . siteURL ) - 1 : ] ; lastChar != "/" {
Config . siteURL = Config . siteURL + "/"
}
2015-09-28 04:17:12 +02:00
2016-06-04 10:22:01 +02:00
parsedUrl , err := url . Parse ( Config . siteURL )
if err != nil {
log . Fatal ( "Could not parse siteurl:" , err )
}
2015-10-30 23:36:47 +01:00
2016-06-04 10:22:01 +02:00
Config . sitePath = parsedUrl . Path
} else {
Config . sitePath = "/"
}
2015-10-30 23:36:47 +01:00
2019-01-14 23:51:02 +01:00
Config . selifPath = strings . TrimLeft ( Config . selifPath , "/" )
if lastChar := Config . selifPath [ len ( Config . selifPath ) - 1 : ] ; lastChar != "/" {
Config . selifPath = Config . selifPath + "/"
}
2019-01-25 08:33:11 +01:00
if Config . s3Bucket != "" {
2019-01-25 09:10:06 +01:00
storageBackend = s3 . NewS3Backend ( Config . s3Bucket , Config . s3Region , Config . s3Endpoint , Config . s3ForcePathStyle )
2019-01-25 08:33:11 +01:00
} else {
storageBackend = localfs . NewLocalfsBackend ( Config . metaDir , Config . filesDir )
2020-05-14 02:37:33 +02:00
if Config . cleanupEveryMinutes > 0 {
go cleanup . PeriodicCleanup ( time . Duration ( Config . cleanupEveryMinutes ) * time . Minute , Config . filesDir , Config . metaDir , Config . noLogs )
}
2019-01-25 08:33:11 +01:00
}
2016-06-07 08:37:42 +02:00
2015-09-29 02:46:58 +02:00
// Template setup
2015-09-29 03:58:50 +02:00
p2l , err := NewPongo2TemplatesLoader ( )
2015-09-29 02:46:58 +02:00
if err != nil {
2015-10-06 08:49:57 +02:00
log . Fatal ( "Error: could not load templates" , err )
2015-09-29 02:46:58 +02:00
}
TemplateSet := pongo2 . NewSet ( "templates" , p2l )
err = populateTemplatesMap ( TemplateSet , Templates )
if err != nil {
2015-10-06 08:49:57 +02:00
log . Fatal ( "Error: could not load templates" , err )
2015-09-29 02:46:58 +02:00
}
2015-09-25 05:21:37 +02:00
2015-09-30 21:54:30 +02:00
staticBox = rice . MustFindBox ( "static" )
timeStarted = time . Now ( )
2015-10-01 16:32:59 +02:00
timeStartedStr = strconv . FormatInt ( timeStarted . Unix ( ) , 10 )
2015-09-30 21:54:30 +02:00
2015-09-25 05:21:37 +02:00
// Routing setup
2015-10-30 23:36:47 +01:00
nameRe := regexp . MustCompile ( "^" + Config . sitePath + ` (?P<name>[a-z0-9-\.]+)$ ` )
2019-01-14 23:51:02 +01:00
selifRe := regexp . MustCompile ( "^" + Config . sitePath + Config . selifPath + ` (?P<name>[a-z0-9-\.]+)$ ` )
selifIndexRe := regexp . MustCompile ( "^" + Config . sitePath + Config . selifPath + ` $ ` )
2015-10-30 23:36:47 +01:00
torrentRe := regexp . MustCompile ( "^" + Config . sitePath + ` (?P<name>[a-z0-9-\.]+)/torrent$ ` )
2015-09-24 07:44:49 +02:00
2020-03-07 00:21:49 +01:00
if Config . authFile == "" || Config . basicAuth {
2015-10-30 23:36:47 +01:00
mux . Get ( Config . sitePath , indexHandler )
mux . Get ( Config . sitePath + "paste/" , pasteHandler )
2015-10-10 21:26:47 +02:00
} else {
2015-10-30 23:36:47 +01:00
mux . Get ( Config . sitePath , http . RedirectHandler ( Config . sitePath + "API" , 303 ) )
mux . Get ( Config . sitePath + "paste/" , http . RedirectHandler ( Config . sitePath + "API/" , 303 ) )
2015-10-10 21:26:47 +02:00
}
2015-10-30 23:36:47 +01:00
mux . Get ( Config . sitePath + "paste" , http . RedirectHandler ( Config . sitePath + "paste/" , 301 ) )
2015-10-10 21:26:47 +02:00
2015-10-30 23:36:47 +01:00
mux . Get ( Config . sitePath + "API/" , apiDocHandler )
mux . Get ( Config . sitePath + "API" , http . RedirectHandler ( Config . sitePath + "API/" , 301 ) )
2015-09-25 18:00:14 +02:00
2015-10-02 02:58:08 +02:00
if Config . remoteUploads {
2015-10-30 23:36:47 +01:00
mux . Get ( Config . sitePath + "upload" , uploadRemote )
mux . Get ( Config . sitePath + "upload/" , uploadRemote )
2015-10-12 04:31:13 +02:00
if Config . remoteAuthFile != "" {
remoteAuthKeys = readAuthKeys ( Config . remoteAuthFile )
}
2015-10-02 02:58:08 +02:00
}
2020-03-07 00:21:49 +01:00
if Config . basicAuth {
options := AuthOptions {
AuthFile : Config . authFile ,
UnauthMethods : [ ] string { } ,
}
2020-03-11 06:45:24 +01:00
okFunc := func ( w http . ResponseWriter , r * http . Request ) {
2020-03-07 00:21:49 +01:00
w . Header ( ) . Set ( "Location" , Config . sitePath )
w . WriteHeader ( http . StatusFound )
}
2020-03-11 06:45:24 +01:00
authHandler := auth {
2020-03-07 00:21:49 +01:00
successHandler : http . HandlerFunc ( okFunc ) ,
failureHandler : http . HandlerFunc ( badAuthorizationHandler ) ,
authKeys : readAuthKeys ( Config . authFile ) ,
o : options ,
}
mux . Head ( Config . sitePath + "auth" , authHandler )
mux . Get ( Config . sitePath + "auth" , authHandler )
}
2015-10-30 23:36:47 +01:00
mux . Post ( Config . sitePath + "upload" , uploadPostHandler )
mux . Post ( Config . sitePath + "upload/" , uploadPostHandler )
mux . Put ( Config . sitePath + "upload" , uploadPutHandler )
mux . Put ( Config . sitePath + "upload/" , uploadPutHandler )
mux . Put ( Config . sitePath + "upload/:name" , uploadPutHandler )
2015-10-14 22:13:29 +02:00
2015-10-30 23:36:47 +01:00
mux . Delete ( Config . sitePath + ":name" , deleteHandler )
2015-10-10 08:04:08 +02:00
2015-10-30 23:36:47 +01:00
mux . Get ( Config . sitePath + "static/*" , staticHandler )
mux . Get ( Config . sitePath + "favicon.ico" , staticHandler )
mux . Get ( Config . sitePath + "robots.txt" , staticHandler )
2020-02-17 15:58:56 +01:00
mux . Get ( nameRe , fileAccessHandler )
mux . Post ( nameRe , fileAccessHandler )
2015-10-10 08:04:08 +02:00
mux . Get ( selifRe , fileServeHandler )
mux . Get ( selifIndexRe , unauthorizedHandler )
mux . Get ( torrentRe , fileTorrentHandler )
2016-06-15 17:42:57 +02:00
2020-03-12 21:32:35 +01:00
if Config . customPagesDir != "" {
initializeCustomPages ( Config . customPagesDir )
for fileName := range customPagesNames {
mux . Get ( Config . sitePath + fileName , makeCustomPageHandler ( fileName ) )
mux . Get ( Config . sitePath + fileName + "/" , makeCustomPageHandler ( fileName ) )
}
}
2015-10-10 08:04:08 +02:00
mux . NotFound ( notFoundHandler )
return mux
2015-09-28 18:30:21 +02:00
}
func main ( ) {
2015-10-10 08:06:28 +02:00
flag . StringVar ( & Config . bind , "bind" , "127.0.0.1:8080" ,
2015-09-28 18:30:21 +02:00
"host to bind to (default: 127.0.0.1:8080)" )
flag . StringVar ( & Config . filesDir , "filespath" , "files/" ,
"path to files directory" )
flag . StringVar ( & Config . metaDir , "metapath" , "meta/" ,
"path to metadata directory" )
2020-03-07 00:21:49 +01:00
flag . BoolVar ( & Config . basicAuth , "basicauth" , false ,
"allow logging by basic auth password" )
2015-09-28 18:30:21 +02:00
flag . BoolVar ( & Config . noLogs , "nologs" , false ,
"remove stdout output for each request" )
2015-09-30 01:28:10 +02:00
flag . BoolVar ( & Config . allowHotlink , "allowhotlink" , false ,
"Allow hotlinking of files" )
2016-06-15 08:21:39 +02:00
flag . StringVar ( & Config . siteName , "sitename" , "" ,
2015-09-28 18:30:21 +02:00
"name of the site" )
2016-06-04 10:22:01 +02:00
flag . StringVar ( & Config . siteURL , "siteurl" , "" ,
2015-09-28 18:30:21 +02:00
"site base url (including trailing slash)" )
2019-01-14 23:51:02 +01:00
flag . StringVar ( & Config . selifPath , "selifpath" , "selif" ,
"path relative to site base url where files are accessed directly" )
2015-10-08 07:38:50 +02:00
flag . Int64Var ( & Config . maxSize , "maxsize" , 4 * 1024 * 1024 * 1024 ,
"maximum upload file size in bytes (default 4GB)" )
2016-09-19 06:45:00 +02:00
flag . Uint64Var ( & Config . maxExpiry , "maxexpiry" , 0 ,
"maximum expiration time in seconds (default is 0, which is no expiry)" )
2015-10-07 18:48:44 +02:00
flag . StringVar ( & Config . certFile , "certfile" , "" ,
"path to ssl certificate (for https)" )
flag . StringVar ( & Config . keyFile , "keyfile" , "" ,
"path to ssl key (for https)" )
2015-10-12 02:32:28 +02:00
flag . BoolVar ( & Config . realIp , "realip" , false ,
"use X-Real-IP/X-Forwarded-For headers as original host" )
2015-10-01 16:32:59 +02:00
flag . BoolVar ( & Config . fastcgi , "fastcgi" , false ,
"serve through fastcgi" )
2015-10-02 02:58:08 +02:00
flag . BoolVar ( & Config . remoteUploads , "remoteuploads" , false ,
"enable remote uploads" )
2015-10-10 21:26:47 +02:00
flag . StringVar ( & Config . authFile , "authfile" , "" ,
"path to a file containing newline-separated scrypted auth keys" )
2015-10-12 04:31:13 +02:00
flag . StringVar ( & Config . remoteAuthFile , "remoteauthfile" , "" ,
"path to a file containing newline-separated scrypted auth keys for remote uploads" )
2015-10-05 04:43:42 +02:00
flag . StringVar ( & Config . contentSecurityPolicy , "contentsecuritypolicy" ,
2019-01-08 20:56:09 +01:00
"default-src 'self'; img-src 'self' data:; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';" ,
2015-10-04 23:58:00 +02:00
"value of default Content-Security-Policy header" )
2015-10-05 04:43:42 +02:00
flag . StringVar ( & Config . fileContentSecurityPolicy , "filecontentsecuritypolicy" ,
2019-01-08 20:56:09 +01:00
"default-src 'none'; img-src 'self'; object-src 'self'; media-src 'self'; style-src 'self' 'unsafe-inline'; frame-ancestors 'self';" ,
2015-10-04 23:58:00 +02:00
"value of Content-Security-Policy header for file access" )
2019-01-08 20:56:09 +01:00
flag . StringVar ( & Config . referrerPolicy , "referrerpolicy" ,
"same-origin" ,
"value of default Referrer-Policy header" )
flag . StringVar ( & Config . fileReferrerPolicy , "filereferrerpolicy" ,
"same-origin" ,
"value of Referrer-Policy header for file access" )
2015-10-05 04:43:42 +02:00
flag . StringVar ( & Config . xFrameOptions , "xframeoptions" , "SAMEORIGIN" ,
2015-10-04 23:58:00 +02:00
"value of X-Frame-Options header" )
2016-06-04 07:49:01 +02:00
flag . Var ( & Config . addHeaders , "addheader" ,
"Add an arbitrary header to the response. This option can be used multiple times." )
2018-11-07 19:13:27 +01:00
flag . BoolVar ( & Config . noDirectAgents , "nodirectagents" , false ,
"disable serving files directly for wget/curl user agents" )
2019-01-25 08:33:11 +01:00
flag . StringVar ( & Config . s3Endpoint , "s3-endpoint" , "" ,
"S3 endpoint" )
flag . StringVar ( & Config . s3Region , "s3-region" , "" ,
"S3 region" )
flag . StringVar ( & Config . s3Bucket , "s3-bucket" , "" ,
"S3 bucket to use for files and metadata" )
2019-01-25 09:10:06 +01:00
flag . BoolVar ( & Config . s3ForcePathStyle , "s3-force-path-style" , false ,
"Force path-style addressing for S3 (e.g. https://s3.amazonaws.com/linx/example.txt)" )
2019-01-26 11:04:32 +01:00
flag . BoolVar ( & Config . forceRandomFilename , "force-random-filename" , false ,
"Force all uploads to use a random filename" )
2020-02-17 15:58:56 +01:00
flag . Uint64Var ( & Config . accessKeyCookieExpiry , "access-cookie-expiry" , 0 , "Expiration time for access key cookies in seconds (set 0 to use session cookies)" )
2020-03-12 21:32:35 +01:00
flag . StringVar ( & Config . customPagesDir , "custompagespath" , "" ,
"path to directory containing .md files to render as custom pages" )
2020-05-14 02:37:33 +02:00
flag . Uint64Var ( & Config . cleanupEveryMinutes , "cleanup-every-minutes" , 0 ,
"How often to clean up expired files in minutes (default is 0, which means files will be cleaned up as they are accessed)" )
2015-10-25 19:04:38 +01:00
iniflags . Parse ( )
2015-09-28 18:30:21 +02:00
2015-10-10 08:04:08 +02:00
mux := setup ( )
2015-09-28 18:30:21 +02:00
2015-10-01 16:32:59 +02:00
if Config . fastcgi {
2019-12-03 03:24:11 +01:00
var listener net . Listener
var err error
if Config . bind [ 0 ] == '/' {
// UNIX path
listener , err = net . ListenUnix ( "unix" , & net . UnixAddr { Name : Config . bind , Net : "unix" } )
cleanup := func ( ) {
log . Print ( "Removing FastCGI socket" )
os . Remove ( Config . bind )
}
defer cleanup ( )
sigs := make ( chan os . Signal , 1 )
signal . Notify ( sigs , syscall . SIGINT , syscall . SIGTERM )
go func ( ) {
sig := <- sigs
log . Print ( "Signal: " , sig )
cleanup ( )
os . Exit ( 0 )
} ( )
} else {
listener , err = net . Listen ( "tcp" , Config . bind )
}
2015-10-07 18:48:44 +02:00
if err != nil {
log . Fatal ( "Could not bind: " , err )
}
2016-06-04 10:22:01 +02:00
log . Printf ( "Serving over fastcgi, bound on %s" , Config . bind )
2015-10-10 08:04:08 +02:00
fcgi . Serve ( listener , mux )
2015-10-07 18:48:44 +02:00
} else if Config . certFile != "" {
2016-06-04 10:22:01 +02:00
log . Printf ( "Serving over https, bound on %s" , Config . bind )
2015-10-10 08:04:08 +02:00
err := graceful . ListenAndServeTLS ( Config . bind , Config . certFile , Config . keyFile , mux )
2015-10-07 18:48:44 +02:00
if err != nil {
log . Fatal ( err )
}
2015-10-01 16:32:59 +02:00
} else {
2016-06-04 10:22:01 +02:00
log . Printf ( "Serving over http, bound on %s" , Config . bind )
2015-10-10 08:04:08 +02:00
err := graceful . ListenAndServe ( Config . bind , mux )
2015-10-07 18:48:44 +02:00
if err != nil {
log . Fatal ( err )
}
2015-10-01 16:32:59 +02:00
}
2015-09-24 07:44:49 +02:00
}