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