2022-09-08 15:12:06 +00:00
/ *
2023-01-18 17:19:29 +00:00
Copyright © 2023 Seednode < seednode @ seedno . de >
2022-09-08 15:12:06 +00:00
* /
package cmd
import (
2023-05-30 23:47:26 +00:00
"bytes"
"embed"
2023-02-05 05:44:31 +00:00
"encoding/gob"
2023-01-21 22:08:59 +00:00
"encoding/json"
2023-05-31 17:43:07 +00:00
"errors"
2022-09-10 23:03:04 +00:00
"fmt"
2022-09-08 15:12:06 +00:00
"io"
2023-05-08 16:45:57 +00:00
"net"
2022-09-08 15:12:06 +00:00
"net/http"
2022-09-08 20:30:51 +00:00
"net/url"
2022-09-08 15:57:59 +00:00
"os"
2023-02-08 13:50:40 +00:00
"os/signal"
2022-09-16 18:45:33 +00:00
"path/filepath"
2022-10-23 18:45:49 +00:00
"regexp"
2022-10-23 21:29:58 +00:00
"runtime"
2023-01-21 22:08:59 +00:00
"sort"
2022-09-08 15:12:06 +00:00
"strconv"
2022-09-08 20:57:52 +00:00
"strings"
2023-01-21 04:42:44 +00:00
"sync"
2023-02-08 13:50:40 +00:00
"syscall"
2022-09-10 23:03:04 +00:00
"time"
2022-11-10 05:17:19 +00:00
2023-09-06 15:15:11 +00:00
"net/http/pprof"
2023-09-06 14:06:44 +00:00
2023-06-03 18:29:49 +00:00
"github.com/julienschmidt/httprouter"
2023-02-05 20:34:22 +00:00
"github.com/klauspost/compress/zstd"
2022-11-10 05:17:19 +00:00
"github.com/yosssi/gohtml"
2022-09-08 15:12:06 +00:00
)
2023-05-31 00:13:02 +00:00
//go:embed favicons/*
2023-05-30 23:47:26 +00:00
var favicons embed . FS
2022-10-20 22:12:29 +00:00
const (
2023-02-18 20:08:11 +00:00
LogDate string = ` 2006-01-02T15:04:05.000-07:00 `
2023-06-03 18:29:49 +00:00
SourcePrefix string = ` /source `
2023-09-11 01:29:11 +00:00
MediaPrefix string = ` /view `
2023-02-18 20:08:11 +00:00
RedirectStatusCode int = http . StatusSeeOther
Timeout time . Duration = 10 * time . Second
2022-10-20 19:22:01 +00:00
2023-09-06 16:53:19 +00:00
FaviconHtml string = ` < link rel = "apple-touch-icon" sizes = "180x180" href = "/favicons/apple-touch-icon.png" >
< link rel = "icon" type = "image/png" sizes = "32x32" href = "/favicons/favicon-32x32.png" >
< link rel = "icon" type = "image/png" sizes = "16x16" href = "/favicons/favicon-16x16.png" >
< link rel = "manifest" href = "/favicons/site.webmanifest" >
< link rel = "mask-icon" href = "/favicons/safari-pinned-tab.svg" color = "#5bbad5" >
< meta name = "msapplication-TileColor" content = "#da532c" >
< meta name = "theme-color" content = "#ffffff" > `
)
2023-05-12 14:05:14 +00:00
2023-01-25 01:06:15 +00:00
type Regexes struct {
2023-01-27 16:06:10 +00:00
alphanumeric * regexp . Regexp
filename * regexp . Regexp
2023-01-25 01:06:15 +00:00
}
2022-10-20 00:27:11 +00:00
type Filters struct {
2023-01-27 16:06:10 +00:00
includes [ ] string
excludes [ ] string
2022-10-20 00:27:11 +00:00
}
func ( f * Filters ) IsEmpty ( ) bool {
2022-12-20 23:12:37 +00:00
return ! ( f . HasIncludes ( ) || f . HasExcludes ( ) )
2022-10-20 00:27:11 +00:00
}
2022-10-20 15:38:32 +00:00
func ( f * Filters ) HasIncludes ( ) bool {
2023-01-27 16:06:10 +00:00
return len ( f . includes ) != 0
2022-10-20 15:38:32 +00:00
}
2023-01-27 16:06:10 +00:00
func ( f * Filters ) Includes ( ) string {
return strings . Join ( f . includes , "," )
2022-10-20 00:27:11 +00:00
}
2022-10-20 15:38:32 +00:00
func ( f * Filters ) HasExcludes ( ) bool {
2023-01-27 16:06:10 +00:00
return len ( f . excludes ) != 0
2022-10-20 15:38:32 +00:00
}
2023-01-27 16:06:10 +00:00
func ( f * Filters ) Excludes ( ) string {
return strings . Join ( f . excludes , "," )
2022-10-20 00:27:11 +00:00
}
2023-01-21 22:08:59 +00:00
type Index struct {
2023-01-27 16:06:10 +00:00
mutex sync . RWMutex
list [ ] string
2023-01-21 22:08:59 +00:00
}
2023-01-27 16:06:10 +00:00
func ( i * Index ) Index ( ) [ ] string {
i . mutex . RLock ( )
val := i . list
i . mutex . RUnlock ( )
2023-01-21 22:08:59 +00:00
return val
}
2023-09-10 01:57:50 +00:00
func ( i * Index ) Remove ( path string ) {
i . mutex . RLock ( )
tempIndex := make ( [ ] string , len ( i . list ) )
copy ( tempIndex , i . list )
i . mutex . RUnlock ( )
var position int
for k , v := range tempIndex {
if path == v {
position = k
break
}
}
tempIndex [ position ] = tempIndex [ len ( tempIndex ) - 1 ]
i . mutex . Lock ( )
i . list = make ( [ ] string , len ( tempIndex ) - 1 )
copy ( i . list , tempIndex [ : len ( tempIndex ) - 1 ] )
i . mutex . Unlock ( )
}
2023-01-27 16:06:10 +00:00
func ( i * Index ) setIndex ( val [ ] string ) {
i . mutex . Lock ( )
i . list = val
i . mutex . Unlock ( )
2023-01-21 22:08:59 +00:00
}
2023-01-27 16:06:10 +00:00
func ( i * Index ) generateCache ( args [ ] string ) {
i . mutex . Lock ( )
i . list = [ ] string { }
i . mutex . Unlock ( )
2023-01-21 22:08:59 +00:00
2023-01-27 16:06:10 +00:00
fileList ( args , & Filters { } , "" , i )
2023-02-05 05:44:31 +00:00
2023-02-05 20:34:22 +00:00
if cache && cacheFile != "" {
i . Export ( cacheFile )
2023-02-05 05:44:31 +00:00
}
2023-01-21 22:08:59 +00:00
}
func ( i * Index ) IsEmpty ( ) bool {
2023-01-27 16:06:10 +00:00
i . mutex . RLock ( )
length := len ( i . list )
i . mutex . RUnlock ( )
2023-01-21 22:08:59 +00:00
return length == 0
}
2023-02-05 05:44:31 +00:00
func ( i * Index ) Export ( path string ) error {
2023-06-13 13:50:13 +00:00
file , err := os . OpenFile ( path , os . O_WRONLY | os . O_CREATE | os . O_TRUNC , 0600 )
2023-02-05 05:44:31 +00:00
if err != nil {
return err
}
defer file . Close ( )
2023-02-05 20:34:22 +00:00
z , err := zstd . NewWriter ( file )
if err != nil {
return err
}
defer z . Close ( )
enc := gob . NewEncoder ( z )
2023-02-05 05:44:31 +00:00
2023-02-08 13:50:40 +00:00
i . mutex . RLock ( )
2023-02-05 05:44:31 +00:00
enc . Encode ( & i . list )
2023-02-08 13:50:40 +00:00
i . mutex . RUnlock ( )
2023-02-05 05:44:31 +00:00
return nil
}
func ( i * Index ) Import ( path string ) error {
file , err := os . OpenFile ( path , os . O_RDONLY , 0600 )
2023-02-08 13:50:40 +00:00
if err != nil {
2023-02-05 05:44:31 +00:00
return err
}
defer file . Close ( )
2023-02-05 20:34:22 +00:00
z , err := zstd . NewReader ( file )
if err != nil {
return err
}
defer z . Close ( )
dec := gob . NewDecoder ( z )
2023-02-05 05:44:31 +00:00
2023-02-08 13:50:40 +00:00
i . mutex . Lock ( )
2023-02-05 05:44:31 +00:00
err = dec . Decode ( & i . list )
2023-02-08 13:50:40 +00:00
i . mutex . Unlock ( )
2023-02-05 05:44:31 +00:00
if err != nil {
return err
}
return nil
}
2023-01-21 22:08:59 +00:00
type ServeStats struct {
2023-01-27 16:06:10 +00:00
mutex sync . RWMutex
list [ ] string
2023-08-13 22:29:28 +00:00
count map [ string ] uint32
2023-01-27 16:06:10 +00:00
size map [ string ] string
times map [ string ] [ ] string
2023-01-21 22:08:59 +00:00
}
2023-02-08 13:50:40 +00:00
type exportedServeStats struct {
List [ ] string
2023-08-13 22:29:28 +00:00
Count map [ string ] uint32
2023-02-08 13:50:40 +00:00
Size map [ string ] string
Times map [ string ] [ ] string
}
2023-09-11 01:29:11 +00:00
func ( s * ServeStats ) incrementCounter ( file string , timestamp time . Time , filesize string ) {
2023-01-27 16:06:10 +00:00
s . mutex . Lock ( )
2023-01-21 22:08:59 +00:00
2023-09-11 01:29:11 +00:00
s . count [ file ] ++
2023-01-21 22:08:59 +00:00
2023-09-11 01:29:11 +00:00
s . times [ file ] = append ( s . times [ file ] , timestamp . Format ( LogDate ) )
2023-01-21 22:08:59 +00:00
2023-09-11 01:29:11 +00:00
_ , exists := s . size [ file ]
2023-01-21 22:08:59 +00:00
if ! exists {
2023-09-11 01:29:11 +00:00
s . size [ file ] = filesize
2023-01-21 22:08:59 +00:00
}
2023-09-11 01:29:11 +00:00
if ! contains ( s . list , file ) {
s . list = append ( s . list , file )
2023-01-21 22:08:59 +00:00
}
2023-01-27 16:06:10 +00:00
s . mutex . Unlock ( )
2023-01-21 22:08:59 +00:00
}
2023-02-08 13:50:40 +00:00
func ( s * ServeStats ) toExported ( ) * exportedServeStats {
stats := & exportedServeStats {
2023-02-18 19:00:08 +00:00
List : make ( [ ] string , len ( s . list ) ) ,
2023-08-13 22:29:28 +00:00
Count : make ( map [ string ] uint32 ) ,
2023-02-08 13:50:40 +00:00
Size : make ( map [ string ] string ) ,
Times : make ( map [ string ] [ ] string ) ,
}
2023-01-27 16:06:10 +00:00
s . mutex . RLock ( )
2023-01-21 22:08:59 +00:00
2023-02-18 19:00:08 +00:00
copy ( stats . List , s . list )
2023-02-08 13:50:40 +00:00
for k , v := range s . count {
stats . Count [ k ] = v
}
for k , v := range s . size {
stats . Size [ k ] = v
}
for k , v := range s . times {
stats . Times [ k ] = v
2023-02-06 18:23:59 +00:00
}
s . mutex . RUnlock ( )
2023-01-21 22:08:59 +00:00
2023-02-08 13:50:40 +00:00
return stats
}
func ( s * ServeStats ) toImported ( stats * exportedServeStats ) {
s . mutex . Lock ( )
2023-02-18 19:00:08 +00:00
s . list = make ( [ ] string , len ( stats . List ) )
copy ( s . list , stats . List )
2023-02-08 13:50:40 +00:00
for k , v := range stats . Count {
s . count [ k ] = v
}
for k , v := range stats . Size {
s . size [ k ] = v
}
for k , v := range stats . Times {
s . times [ k ] = v
}
s . mutex . Unlock ( )
}
2023-09-11 01:29:11 +00:00
func ( s * ServeStats ) ListFiles ( page int ) ( [ ] byte , error ) {
2023-02-08 13:50:40 +00:00
stats := s . toExported ( )
sort . SliceStable ( stats . List , func ( p , q int ) bool {
2023-06-20 14:02:13 +00:00
return strings . ToLower ( stats . List [ p ] ) < strings . ToLower ( stats . List [ q ] )
2023-01-21 22:08:59 +00:00
} )
2023-09-09 05:06:28 +00:00
var startIndex , stopIndex int
2023-01-21 22:08:59 +00:00
2023-09-09 05:06:28 +00:00
if page == - 1 {
startIndex = 0
stopIndex = len ( stats . List ) - 1
} else {
startIndex = ( ( page - 1 ) * int ( pageLength ) )
stopIndex = ( startIndex + int ( pageLength ) )
}
if startIndex > len ( stats . List ) - 1 {
return [ ] byte ( "{}" ) , nil
}
if stopIndex > len ( stats . List ) - 1 {
stopIndex = len ( stats . List ) - 1
}
a := make ( [ ] timesServed , stopIndex - startIndex )
for k , v := range stats . List [ startIndex : stopIndex ] {
2023-02-18 19:00:08 +00:00
a [ k ] = timesServed { v , stats . Count [ v ] , stats . Size [ v ] , stats . Times [ v ] }
2023-01-21 22:08:59 +00:00
}
r , err := json . MarshalIndent ( a , "" , " " )
if err != nil {
return [ ] byte { } , err
}
return r , nil
}
2023-02-08 13:50:40 +00:00
func ( s * ServeStats ) Export ( path string ) error {
2023-06-13 13:50:13 +00:00
file , err := os . OpenFile ( path , os . O_WRONLY | os . O_CREATE | os . O_TRUNC , 0600 )
2023-02-08 13:50:40 +00:00
if err != nil {
return err
}
defer file . Close ( )
z , err := zstd . NewWriter ( file )
if err != nil {
return err
}
defer z . Close ( )
enc := gob . NewEncoder ( z )
stats := s . toExported ( )
err = enc . Encode ( & stats )
if err != nil {
2023-06-20 15:24:12 +00:00
return err
2023-02-08 13:50:40 +00:00
}
return nil
}
func ( s * ServeStats ) Import ( path string ) error {
file , err := os . OpenFile ( path , os . O_RDONLY , 0600 )
if err != nil {
return err
}
defer file . Close ( )
z , err := zstd . NewReader ( file )
if err != nil {
return err
}
defer z . Close ( )
dec := gob . NewDecoder ( z )
stats := & exportedServeStats {
List : [ ] string { } ,
2023-08-13 22:29:28 +00:00
Count : make ( map [ string ] uint32 ) ,
2023-02-08 13:50:40 +00:00
Size : make ( map [ string ] string ) ,
Times : make ( map [ string ] [ ] string ) ,
}
err = dec . Decode ( stats )
if err != nil {
return err
}
s . toImported ( stats )
return nil
}
2023-01-27 16:06:10 +00:00
type timesServed struct {
2023-01-25 01:06:15 +00:00
File string
2023-08-13 22:29:28 +00:00
Served uint32
2023-01-25 01:06:15 +00:00
Size string
Times [ ] string
2023-01-24 18:03:26 +00:00
}
2023-08-06 02:38:28 +00:00
func newErrorPage ( title , body string ) string {
var htmlBody strings . Builder
htmlBody . WriteString ( ` <!DOCTYPE html><html lang="en"><head> ` )
2023-09-06 16:53:19 +00:00
htmlBody . WriteString ( FaviconHtml )
2023-08-06 02:38:28 +00:00
htmlBody . WriteString ( ` <style>a { display:block;height:100%;width:100%;text-decoration:none;color:inherit;cursor:auto;}</style> ` )
htmlBody . WriteString ( fmt . Sprintf ( "<title>%s</title></head>" , title ) )
htmlBody . WriteString ( fmt . Sprintf ( "<body><a href=\"/\">%s</a></body></html>" , body ) )
return htmlBody . String ( )
}
2022-11-10 06:26:21 +00:00
func notFound ( w http . ResponseWriter , r * http . Request , filePath string ) error {
startTime := time . Now ( )
2022-09-10 23:03:04 +00:00
2023-01-27 19:17:13 +00:00
if verbose {
2023-01-24 16:13:09 +00:00
fmt . Printf ( "%s | Unavailable file %s requested by %s\n" ,
2023-01-25 01:06:15 +00:00
startTime . Format ( LogDate ) ,
2022-11-10 06:26:21 +00:00
filePath ,
r . RemoteAddr ,
)
2022-09-26 17:31:45 +00:00
}
2023-06-03 20:51:08 +00:00
w . WriteHeader ( http . StatusNotFound )
2022-10-29 00:09:05 +00:00
w . Header ( ) . Add ( "Content-Type" , "text/html" )
2023-08-06 02:38:28 +00:00
_ , err := io . WriteString ( w , gohtml . Format ( newErrorPage ( "Not Found" , "404 Page not found" ) ) )
2022-10-29 00:09:05 +00:00
if err != nil {
return err
}
return nil
}
2023-08-06 02:38:28 +00:00
func serverError ( w http . ResponseWriter , r * http . Request , i interface { } ) {
2023-06-20 15:24:12 +00:00
startTime := time . Now ( )
if verbose {
fmt . Printf ( "%s | Invalid request for %s from %s\n" ,
startTime . Format ( LogDate ) ,
r . URL . Path ,
r . RemoteAddr ,
)
}
w . WriteHeader ( http . StatusInternalServerError )
w . Header ( ) . Add ( "Content-Type" , "text/html" )
2023-08-06 02:38:28 +00:00
io . WriteString ( w , gohtml . Format ( newErrorPage ( "Server Error" , "500 Internal Server Error" ) ) )
2023-06-20 15:24:12 +00:00
}
func serverErrorHandler ( ) func ( http . ResponseWriter , * http . Request , interface { } ) {
2023-08-06 02:38:28 +00:00
return serverError
2023-06-03 18:29:49 +00:00
}
2023-09-06 16:53:19 +00:00
func RefreshInterval ( r * http . Request ) ( int64 , string ) {
2023-08-21 20:38:56 +00:00
var interval string
2022-11-10 16:09:39 +00:00
2023-08-21 20:38:56 +00:00
if refreshInterval == "" {
interval = r . URL . Query ( ) . Get ( "refresh" )
} else {
interval = refreshInterval
2023-01-18 15:58:14 +00:00
}
2022-11-11 15:19:53 +00:00
2023-08-21 20:38:56 +00:00
duration , err := time . ParseDuration ( interval )
switch {
case err != nil || duration == 0 :
2023-01-18 15:58:14 +00:00
return 0 , "0ms"
2023-08-21 20:38:56 +00:00
case duration < 500 * time . Millisecond :
return 500 , "500ms"
default :
return duration . Milliseconds ( ) , interval
2022-11-10 16:09:39 +00:00
}
2023-01-24 18:03:26 +00:00
}
2023-05-08 18:12:51 +00:00
func SortOrder ( r * http . Request ) string {
2022-11-10 16:09:39 +00:00
sortOrder := r . URL . Query ( ) . Get ( "sort" )
2022-11-11 15:19:53 +00:00
if sortOrder == "asc" || sortOrder == "desc" {
return sortOrder
2022-11-10 16:09:39 +00:00
}
2022-11-11 15:19:53 +00:00
return ""
2022-11-10 16:09:39 +00:00
}
2023-01-27 16:06:10 +00:00
func splitQueryParams ( query string , Regexes * Regexes ) [ ] string {
2022-11-10 19:57:38 +00:00
results := [ ] string { }
2022-10-20 01:37:12 +00:00
if query == "" {
2022-11-10 19:57:38 +00:00
return results
2022-10-20 01:37:12 +00:00
}
2022-10-20 15:38:32 +00:00
params := strings . Split ( query , "," )
for i := 0 ; i < len ( params ) ; i ++ {
2023-01-27 16:06:10 +00:00
if Regexes . alphanumeric . MatchString ( params [ i ] ) {
2022-11-10 19:57:38 +00:00
results = append ( results , strings . ToLower ( params [ i ] ) )
2022-11-10 16:09:39 +00:00
}
2022-10-20 15:38:32 +00:00
}
2022-11-10 19:57:38 +00:00
return results
2022-10-20 01:37:12 +00:00
}
2022-11-10 06:04:47 +00:00
func generateQueryParams ( filters * Filters , sortOrder , refreshInterval string ) string {
2022-11-09 01:24:49 +00:00
var hasParams bool
2022-11-10 06:04:47 +00:00
var queryParams strings . Builder
queryParams . WriteString ( "?" )
2022-11-09 01:24:49 +00:00
2023-01-27 19:17:13 +00:00
if filtering {
2022-11-10 06:04:47 +00:00
queryParams . WriteString ( "include=" )
2022-11-09 01:24:49 +00:00
if filters . HasIncludes ( ) {
2023-01-27 16:06:10 +00:00
queryParams . WriteString ( filters . Includes ( ) )
2022-11-09 01:24:49 +00:00
}
2022-11-10 06:04:47 +00:00
queryParams . WriteString ( "&exclude=" )
2022-11-09 01:24:49 +00:00
if filters . HasExcludes ( ) {
2023-01-27 16:06:10 +00:00
queryParams . WriteString ( filters . Excludes ( ) )
2022-11-09 01:24:49 +00:00
}
hasParams = true
}
2023-01-27 19:17:13 +00:00
if sorting {
2022-11-09 01:24:49 +00:00
if hasParams {
2022-11-10 06:04:47 +00:00
queryParams . WriteString ( "&" )
2022-11-09 01:24:49 +00:00
}
2023-01-24 16:13:09 +00:00
queryParams . WriteString ( fmt . Sprintf ( "sort=%s" , sortOrder ) )
2022-11-09 01:24:49 +00:00
hasParams = true
}
2022-11-10 06:04:47 +00:00
if hasParams {
queryParams . WriteString ( "&" )
2022-10-20 01:37:12 +00:00
}
2023-01-24 16:13:09 +00:00
queryParams . WriteString ( fmt . Sprintf ( "refresh=%s" , refreshInterval ) )
2022-10-20 01:37:12 +00:00
2022-11-10 06:04:47 +00:00
return queryParams . String ( )
2022-10-20 01:37:12 +00:00
}
2023-01-25 01:06:15 +00:00
func stripQueryParams ( u string ) ( string , error ) {
uri , err := url . Parse ( u )
if err != nil {
return "" , err
}
uri . RawQuery = ""
escapedUri , err := url . QueryUnescape ( uri . String ( ) )
if err != nil {
return "" , err
}
if runtime . GOOS == "windows" {
return strings . TrimPrefix ( escapedUri , "/" ) , nil
}
return escapedUri , nil
}
func generateFilePath ( filePath string ) string {
var htmlBody strings . Builder
2023-06-03 18:29:49 +00:00
htmlBody . WriteString ( SourcePrefix )
2023-01-25 01:06:15 +00:00
if runtime . GOOS == "windows" {
htmlBody . WriteString ( ` / ` )
}
htmlBody . WriteString ( filePath )
return htmlBody . String ( )
}
func refererToUri ( referer string ) string {
parts := strings . SplitAfterN ( referer , "/" , 4 )
if len ( parts ) < 4 {
return ""
}
return "/" + parts [ 3 ]
}
2023-01-27 16:06:10 +00:00
func realIP ( r * http . Request ) string {
2023-01-25 01:06:15 +00:00
remoteAddr := strings . SplitAfter ( r . RemoteAddr , ":" )
if len ( remoteAddr ) < 1 {
return r . RemoteAddr
}
remotePort := remoteAddr [ len ( remoteAddr ) - 1 ]
cfIP := r . Header . Get ( "Cf-Connecting-Ip" )
xRealIp := r . Header . Get ( "X-Real-Ip" )
switch {
case cfIP != "" :
return cfIP + ":" + remotePort
case xRealIp != "" :
return xRealIp + ":" + remotePort
default :
return r . RemoteAddr
}
}
2023-06-03 18:29:49 +00:00
func serveCacheClear ( args [ ] string , index * Index ) httprouter . Handle {
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
2023-01-27 16:06:10 +00:00
index . generateCache ( args )
2023-01-25 01:06:15 +00:00
w . Header ( ) . Set ( "Content-Type" , "text/plain" )
2023-06-03 23:45:32 +00:00
2023-01-25 01:06:15 +00:00
w . Write ( [ ] byte ( "Ok" ) )
2023-01-18 18:11:23 +00:00
}
}
2023-06-03 18:29:49 +00:00
func serveStats ( args [ ] string , stats * ServeStats ) httprouter . Handle {
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
2023-01-19 18:07:15 +00:00
2023-01-21 22:37:32 +00:00
startTime := time . Now ( )
2023-09-09 05:25:04 +00:00
page , err := strconv . Atoi ( p . ByName ( "page" ) )
2023-09-09 05:06:28 +00:00
if err != nil || page == 0 {
page = - 1
}
2023-09-11 01:29:11 +00:00
response , err := stats . ListFiles ( page )
2023-01-19 18:07:15 +00:00
if err != nil {
2023-02-07 13:39:04 +00:00
fmt . Println ( err )
2023-06-03 23:45:32 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-02-07 13:39:04 +00:00
return
2023-01-19 18:07:15 +00:00
}
2023-09-10 01:57:50 +00:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
2023-01-21 22:37:32 +00:00
w . Write ( response )
2023-01-27 19:17:13 +00:00
if verbose {
2023-01-24 16:13:09 +00:00
fmt . Printf ( "%s | Served statistics page (%s) to %s in %s\n" ,
2023-01-25 01:06:15 +00:00
startTime . Format ( LogDate ) ,
2023-01-21 22:37:32 +00:00
humanReadableSize ( len ( response ) ) ,
2023-01-27 16:06:10 +00:00
realIP ( r ) ,
2023-01-21 22:37:32 +00:00
time . Since ( startTime ) . Round ( time . Microsecond ) ,
)
}
2023-02-08 13:50:40 +00:00
if statisticsFile != "" {
stats . Export ( statisticsFile )
}
2023-01-19 18:07:15 +00:00
}
}
2023-09-09 05:06:28 +00:00
func serveDebugHtml ( args [ ] string , index * Index , paginate bool ) httprouter . Handle {
2023-06-03 18:29:49 +00:00
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
2023-05-08 14:10:13 +00:00
w . Header ( ) . Set ( "Content-Type" , "text/html" )
startTime := time . Now ( )
indexDump := index . Index ( )
2023-09-10 01:57:50 +00:00
fileCount := len ( indexDump )
2023-09-09 05:06:28 +00:00
var startIndex , stopIndex int
page , err := strconv . Atoi ( p . ByName ( "page" ) )
if err != nil || page <= 0 {
startIndex = 0
2023-09-10 01:57:50 +00:00
stopIndex = fileCount
2023-09-09 05:06:28 +00:00
} else {
startIndex = ( ( page - 1 ) * int ( pageLength ) )
stopIndex = ( startIndex + int ( pageLength ) )
}
2023-09-10 01:57:50 +00:00
if startIndex > ( fileCount - 1 ) {
2023-09-09 05:06:28 +00:00
indexDump = [ ] string { }
}
2023-09-10 01:57:50 +00:00
if stopIndex > fileCount {
stopIndex = fileCount
2023-09-09 05:06:28 +00:00
}
2023-05-08 14:10:13 +00:00
sort . SliceStable ( indexDump , func ( p , q int ) bool {
2023-06-19 19:34:30 +00:00
return strings . ToLower ( indexDump [ p ] ) < strings . ToLower ( indexDump [ q ] )
2023-05-08 14:10:13 +00:00
} )
var htmlBody strings . Builder
htmlBody . WriteString ( ` <!DOCTYPE html><html lang="en"><head> ` )
2023-09-06 16:53:19 +00:00
htmlBody . WriteString ( FaviconHtml )
2023-08-08 22:12:34 +00:00
htmlBody . WriteString ( ` <style>a { text-decoration:none;height:100%;width:100%;color:inherit;cursor:pointer} ` )
htmlBody . WriteString ( ` table,td,tr { border:1px solid black;border-collapse:collapse}td { white-space:nowrap;padding:.5em}</style> ` )
2023-09-10 01:57:50 +00:00
htmlBody . WriteString ( fmt . Sprintf ( "<title>Index contains %d files</title></head><body><table>" , fileCount ) )
2023-09-09 05:06:28 +00:00
if len ( indexDump ) > 0 {
for _ , v := range indexDump [ startIndex : stopIndex ] {
var shouldSort = ""
if sorting {
shouldSort = "?sort=asc"
}
2023-09-11 01:29:11 +00:00
htmlBody . WriteString ( fmt . Sprintf ( "<tr><td><a href=\"%s%s%s\">%s</a></td></tr>\n" , MediaPrefix , v , shouldSort , v ) )
2023-09-09 05:06:28 +00:00
}
}
if pageLength != 0 {
nextPage := page + 1
2023-09-10 04:27:40 +00:00
if nextPage > ( fileCount / int ( pageLength ) ) && fileCount % int ( pageLength ) == 0 {
2023-09-10 01:57:50 +00:00
nextPage = fileCount / int ( pageLength )
2023-09-10 04:27:40 +00:00
} else if nextPage > ( fileCount / int ( pageLength ) ) {
nextPage = ( fileCount / int ( pageLength ) ) + 1
2023-09-09 05:06:28 +00:00
}
prevPage := page - 1
if prevPage < 1 {
prevPage = 1
}
2023-05-09 01:21:39 +00:00
2023-09-09 05:06:28 +00:00
if paginate {
htmlBody . WriteString ( fmt . Sprintf ( "<button onclick=\"window.location.href = '/html/%d';\">Prev</button>" , prevPage ) )
htmlBody . WriteString ( fmt . Sprintf ( "<button onclick=\"window.location.href = '/html/%d';\">Next</button>" , nextPage ) )
2023-05-09 01:21:39 +00:00
}
2023-05-08 14:10:13 +00:00
}
2023-09-09 05:06:28 +00:00
2023-06-14 00:40:29 +00:00
htmlBody . WriteString ( ` </table></body></html> ` )
2023-05-08 14:10:13 +00:00
b , err := io . WriteString ( w , gohtml . Format ( htmlBody . String ( ) ) )
if err != nil {
return
}
if verbose {
fmt . Printf ( "%s | Served HTML debug page (%s) to %s in %s\n" ,
startTime . Format ( LogDate ) ,
humanReadableSize ( b ) ,
realIP ( r ) ,
time . Since ( startTime ) . Round ( time . Microsecond ) ,
)
}
}
}
2023-06-03 18:29:49 +00:00
func serveDebugJson ( args [ ] string , index * Index ) httprouter . Handle {
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
2023-04-28 18:19:07 +00:00
w . Header ( ) . Set ( "Content-Type" , "application/json" )
startTime := time . Now ( )
2023-04-28 18:26:47 +00:00
indexDump := index . Index ( )
2023-09-10 01:57:50 +00:00
fileCount := len ( indexDump )
2023-04-28 18:26:47 +00:00
sort . SliceStable ( indexDump , func ( p , q int ) bool {
2023-06-20 14:02:13 +00:00
return strings . ToLower ( indexDump [ p ] ) < strings . ToLower ( indexDump [ q ] )
2023-04-28 18:26:47 +00:00
} )
2023-09-09 05:06:28 +00:00
var startIndex , stopIndex int
page , err := strconv . Atoi ( p . ByName ( "page" ) )
if err != nil || page <= 0 {
startIndex = 0
2023-09-10 01:57:50 +00:00
stopIndex = fileCount
2023-09-09 05:06:28 +00:00
} else {
startIndex = ( ( page - 1 ) * int ( pageLength ) )
stopIndex = ( startIndex + int ( pageLength ) )
}
2023-09-10 01:57:50 +00:00
if startIndex > ( fileCount - 1 ) {
2023-09-09 05:06:28 +00:00
indexDump = [ ] string { }
}
2023-09-10 01:57:50 +00:00
if stopIndex > fileCount {
stopIndex = fileCount
2023-09-09 05:06:28 +00:00
}
response , err := json . MarshalIndent ( indexDump [ startIndex : stopIndex ] , "" , " " )
2023-04-28 18:19:07 +00:00
if err != nil {
2023-06-20 15:24:12 +00:00
fmt . Println ( err )
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-04-28 18:19:07 +00:00
return
}
w . Write ( response )
if verbose {
2023-05-08 14:10:13 +00:00
fmt . Printf ( "%s | Served JSON debug page (%s) to %s in %s\n" ,
2023-04-28 18:19:07 +00:00
startTime . Format ( LogDate ) ,
humanReadableSize ( len ( response ) ) ,
realIP ( r ) ,
time . Since ( startTime ) . Round ( time . Microsecond ) ,
)
}
}
}
2023-09-10 02:17:17 +00:00
func serveStaticFile ( paths [ ] string , stats * ServeStats , index * Index ) httprouter . Handle {
2023-06-03 18:29:49 +00:00
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
path := strings . TrimPrefix ( r . URL . Path , SourcePrefix )
prefixedFilePath , err := stripQueryParams ( path )
2023-05-08 18:12:51 +00:00
if err != nil {
fmt . Println ( err )
2023-06-03 23:45:32 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-05-08 18:12:51 +00:00
return
}
2023-06-03 18:29:49 +00:00
filePath , err := filepath . EvalSymlinks ( strings . TrimPrefix ( prefixedFilePath , SourcePrefix ) )
2023-05-08 18:12:51 +00:00
if err != nil {
fmt . Println ( err )
2023-06-03 23:45:32 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-05-08 18:12:51 +00:00
return
}
if ! pathIsValid ( filePath , paths ) {
notFound ( w , r , filePath )
return
}
exists , err := fileExists ( filePath )
2023-01-25 01:06:15 +00:00
if err != nil {
2023-02-07 13:39:04 +00:00
fmt . Println ( err )
2023-06-03 23:45:32 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-05-08 18:12:51 +00:00
return
}
if ! exists {
notFound ( w , r , filePath )
return
}
startTime := time . Now ( )
buf , err := os . ReadFile ( filePath )
if err != nil {
fmt . Println ( err )
2023-06-03 23:45:32 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-05-08 18:12:51 +00:00
return
}
w . Write ( buf )
fileSize := humanReadableSize ( len ( buf ) )
2023-09-10 01:57:50 +00:00
if russian {
err = os . Remove ( filePath )
if err != nil {
fmt . Println ( err )
serverError ( w , r , nil )
return
}
2023-09-11 02:28:27 +00:00
if cache {
index . Remove ( filePath )
}
2023-09-10 01:57:50 +00:00
}
2023-05-08 18:12:51 +00:00
if verbose {
fmt . Printf ( "%s | Served %s (%s) to %s in %s\n" ,
startTime . Format ( LogDate ) ,
filePath ,
fileSize ,
realIP ( r ) ,
time . Since ( startTime ) . Round ( time . Microsecond ) ,
)
}
if statistics {
stats . incrementCounter ( filePath , startTime , fileSize )
2023-01-25 01:06:15 +00:00
}
2023-09-10 01:57:50 +00:00
2023-01-20 02:31:02 +00:00
}
2023-01-25 01:06:15 +00:00
}
2023-01-20 02:31:02 +00:00
2023-06-03 18:29:49 +00:00
func serveRoot ( paths [ ] string , Regexes * Regexes , index * Index ) httprouter . Handle {
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
2023-01-25 01:06:15 +00:00
refererUri , err := stripQueryParams ( refererToUri ( r . Referer ( ) ) )
if err != nil {
2023-02-07 13:39:04 +00:00
fmt . Println ( err )
2023-06-03 23:37:06 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-02-07 13:39:04 +00:00
return
2023-01-25 01:06:15 +00:00
}
2023-01-21 04:42:44 +00:00
2023-09-11 01:29:11 +00:00
strippedRefererUri := strings . TrimPrefix ( refererUri , MediaPrefix )
2023-06-03 23:37:06 +00:00
2023-01-25 01:06:15 +00:00
filters := & Filters {
2023-01-27 16:06:10 +00:00
includes : splitQueryParams ( r . URL . Query ( ) . Get ( "include" ) , Regexes ) ,
excludes : splitQueryParams ( r . URL . Query ( ) . Get ( "exclude" ) , Regexes ) ,
2023-01-25 01:06:15 +00:00
}
2023-05-08 18:12:51 +00:00
sortOrder := SortOrder ( r )
2023-01-25 01:06:15 +00:00
2023-09-06 16:53:19 +00:00
_ , refreshInterval := RefreshInterval ( r )
2023-01-25 01:06:15 +00:00
2023-06-03 18:29:49 +00:00
var filePath string
2023-01-25 01:06:15 +00:00
2023-06-03 18:29:49 +00:00
if refererUri != "" {
2023-06-03 23:37:06 +00:00
filePath , err = nextFile ( strippedRefererUri , sortOrder , Regexes )
2023-06-03 18:29:49 +00:00
if err != nil {
fmt . Println ( err )
2023-06-03 23:37:06 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-06-03 18:29:49 +00:00
return
2023-01-25 01:06:15 +00:00
}
2023-06-03 18:29:49 +00:00
}
2023-01-25 01:06:15 +00:00
2023-06-03 18:29:49 +00:00
loop :
for timeout := time . After ( Timeout ) ; ; {
select {
case <- timeout :
break loop
default :
2023-01-25 01:06:15 +00:00
}
2023-06-03 18:29:49 +00:00
if filePath != "" {
break loop
2023-01-25 01:06:15 +00:00
}
2023-06-03 18:29:49 +00:00
filePath , err = newFile ( paths , filters , sortOrder , Regexes , index )
switch {
2023-09-11 01:29:11 +00:00
case err != nil && err == ErrNoMediaFound :
2023-01-25 01:06:15 +00:00
notFound ( w , r , filePath )
return
2023-06-03 18:29:49 +00:00
case err != nil :
2023-02-07 13:39:04 +00:00
fmt . Println ( err )
2023-06-03 23:37:06 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-02-07 13:39:04 +00:00
return
2023-01-25 01:06:15 +00:00
}
2023-06-03 18:29:49 +00:00
}
2023-05-09 01:05:10 +00:00
2023-06-03 18:29:49 +00:00
queryParams := generateQueryParams ( filters , sortOrder , refreshInterval )
2023-01-25 01:06:15 +00:00
2023-06-03 18:29:49 +00:00
newUrl := fmt . Sprintf ( "http://%s%s%s" ,
r . Host ,
preparePath ( filePath ) ,
queryParams ,
)
http . Redirect ( w , r , newUrl , RedirectStatusCode )
}
}
2023-01-25 01:06:15 +00:00
2023-09-11 01:29:11 +00:00
func serveMedia ( paths [ ] string , Regexes * Regexes , index * Index ) httprouter . Handle {
2023-06-03 18:29:49 +00:00
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
filters := & Filters {
includes : splitQueryParams ( r . URL . Query ( ) . Get ( "include" ) , Regexes ) ,
excludes : splitQueryParams ( r . URL . Query ( ) . Get ( "exclude" ) , Regexes ) ,
}
2023-05-09 01:05:10 +00:00
2023-06-03 18:29:49 +00:00
sortOrder := SortOrder ( r )
2023-01-25 01:06:15 +00:00
2023-09-11 01:29:11 +00:00
filePath := strings . TrimPrefix ( r . URL . Path , MediaPrefix )
2023-05-08 18:12:51 +00:00
2023-06-03 18:29:49 +00:00
if runtime . GOOS == "windows" {
filePath = strings . TrimPrefix ( filePath , "/" )
}
2023-05-08 18:12:51 +00:00
2023-06-03 18:29:49 +00:00
exists , err := fileExists ( filePath )
if err != nil {
fmt . Println ( err )
2023-06-03 23:37:06 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-06-03 18:29:49 +00:00
return
}
if ! exists {
notFound ( w , r , filePath )
return
}
2023-05-08 18:12:51 +00:00
2023-09-11 02:02:03 +00:00
supported , fileType , mime , err := fileType ( filePath )
2023-06-03 18:29:49 +00:00
if err != nil {
fmt . Println ( err )
2023-06-03 23:37:06 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-06-03 18:29:49 +00:00
return
}
2023-05-08 18:12:51 +00:00
2023-09-11 01:23:48 +00:00
if ! supported {
2023-06-03 18:29:49 +00:00
notFound ( w , r , filePath )
2023-05-08 18:12:51 +00:00
2023-06-03 18:29:49 +00:00
return
}
2023-09-10 16:27:55 +00:00
dimensions , err := imageDimensions ( filePath )
if err != nil {
fmt . Println ( err )
2023-06-03 23:37:06 +00:00
2023-09-10 16:27:55 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-09-10 16:27:55 +00:00
return
2023-01-25 01:06:15 +00:00
}
2023-06-03 18:29:49 +00:00
fileName := filepath . Base ( filePath )
w . Header ( ) . Add ( "Content-Type" , "text/html" )
2023-09-06 16:53:19 +00:00
refreshTimer , refreshInterval := RefreshInterval ( r )
2023-06-03 18:29:49 +00:00
queryParams := generateQueryParams ( filters , sortOrder , refreshInterval )
var htmlBody strings . Builder
htmlBody . WriteString ( ` <!DOCTYPE html><html lang="en"><head> ` )
2023-09-06 16:53:19 +00:00
htmlBody . WriteString ( FaviconHtml )
2023-06-03 18:29:49 +00:00
htmlBody . WriteString ( ` <style>html,body { margin:0;padding:0;height:100%;} ` )
htmlBody . WriteString ( ` a { display:block;height:100%;width:100%;text-decoration:none;} ` )
htmlBody . WriteString ( ` img { margin:auto;display:block;max-width:97%;max-height:97%;object-fit:scale-down; ` )
htmlBody . WriteString ( ` position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}</style> ` )
2023-09-11 01:23:48 +00:00
switch fileType {
case "image" :
htmlBody . WriteString ( fmt . Sprintf ( ` <title>%s (%dx%d)</title> ` ,
fileName ,
dimensions . width ,
dimensions . height ) )
2023-09-11 02:02:03 +00:00
default :
2023-09-11 01:23:48 +00:00
htmlBody . WriteString ( fmt . Sprintf ( ` <title>%s</title> ` ,
fileName ) )
}
2023-06-03 18:29:49 +00:00
htmlBody . WriteString ( ` </head><body> ` )
if refreshInterval != "0ms" {
htmlBody . WriteString ( fmt . Sprintf ( "<script>window.onload = function(){setInterval(function(){window.location.href = '/%s';}, %d);};</script>" ,
queryParams ,
refreshTimer ) )
}
2023-09-11 01:23:48 +00:00
switch fileType {
2023-09-11 02:02:03 +00:00
case "audio" :
htmlBody . WriteString ( fmt . Sprintf ( ` <a href="/%s"><audio controls autoplay><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the audio tag.</audio></a> ` ,
queryParams ,
generateFilePath ( filePath ) ,
mime ,
fileName ) )
2023-09-11 01:23:48 +00:00
case "image" :
2023-09-11 01:41:08 +00:00
htmlBody . WriteString ( fmt . Sprintf ( ` <a href="/%s"><img src="%s" width="%d" height="%d" type="%s" alt="Roulette selected: %s"></a> ` ,
2023-09-11 01:23:48 +00:00
queryParams ,
generateFilePath ( filePath ) ,
dimensions . width ,
dimensions . height ,
2023-09-11 01:41:08 +00:00
mime ,
2023-09-11 01:23:48 +00:00
fileName ) )
case "video" :
2023-09-11 01:41:08 +00:00
htmlBody . WriteString ( fmt . Sprintf ( ` <a href="/%s"><video controls autoplay><source src="%s" type="%s" alt="Roulette selected: %s">Your browser does not support the video tag.</video></a> ` ,
2023-09-11 01:23:48 +00:00
queryParams ,
generateFilePath ( filePath ) ,
2023-09-11 01:41:08 +00:00
mime ,
2023-09-11 01:23:48 +00:00
fileName ) )
}
2023-06-03 18:29:49 +00:00
2023-09-11 02:02:03 +00:00
htmlBody . WriteString ( ` </body></html> ` )
2023-06-03 18:29:49 +00:00
_ , err = io . WriteString ( w , gohtml . Format ( htmlBody . String ( ) ) )
if err != nil {
fmt . Println ( err )
2023-06-03 23:37:06 +00:00
2023-08-06 02:38:28 +00:00
serverError ( w , r , nil )
2023-06-20 15:24:12 +00:00
2023-06-03 18:29:49 +00:00
return
}
2023-01-25 01:06:15 +00:00
}
}
2023-06-03 18:29:49 +00:00
func serveFavicons ( ) httprouter . Handle {
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
2023-05-31 00:13:02 +00:00
fname := strings . TrimPrefix ( r . URL . Path , "/" )
2023-01-25 01:06:15 +00:00
2023-05-31 00:13:02 +00:00
data , err := favicons . ReadFile ( fname )
2023-05-30 23:47:26 +00:00
if err != nil {
return
}
w . Header ( ) . Write ( bytes . NewBufferString ( "Content-Length: " + strconv . Itoa ( len ( data ) ) ) )
2023-06-03 23:37:06 +00:00
2023-05-30 23:47:26 +00:00
w . Write ( data )
2023-05-31 00:13:02 +00:00
}
2023-05-30 23:47:26 +00:00
}
2023-01-25 01:06:15 +00:00
2023-06-03 20:51:08 +00:00
func serveVersion ( ) httprouter . Handle {
return func ( w http . ResponseWriter , r * http . Request , p httprouter . Params ) {
data := [ ] byte ( fmt . Sprintf ( "roulette v%s\n" , Version ) )
w . Header ( ) . Write ( bytes . NewBufferString ( "Content-Length: " + strconv . Itoa ( len ( data ) ) ) )
2023-06-03 23:37:06 +00:00
2023-06-03 20:51:08 +00:00
w . Write ( data )
}
}
2023-01-25 01:06:15 +00:00
func ServePage ( args [ ] string ) error {
2023-07-17 12:41:32 +00:00
timeZone := os . Getenv ( "TZ" )
if timeZone != "" {
var err error
time . Local , err = time . LoadLocation ( timeZone )
if err != nil {
return err
}
}
2023-05-08 16:45:57 +00:00
bindHost , err := net . LookupHost ( bind )
if err != nil {
return err
}
bindAddr := net . ParseIP ( bindHost [ 0 ] )
if bindAddr == nil {
2023-05-31 18:01:24 +00:00
return errors . New ( "invalid bind address provided" )
2023-05-08 16:45:57 +00:00
}
2023-01-25 01:06:15 +00:00
paths , err := normalizePaths ( args )
2023-01-24 18:03:26 +00:00
if err != nil {
2023-01-25 01:06:15 +00:00
return err
2023-01-20 02:31:02 +00:00
}
2023-04-11 09:44:18 +00:00
if len ( paths ) == 0 {
2023-09-11 02:08:51 +00:00
return ErrNoMediaFound
2023-04-11 09:44:18 +00:00
}
2023-09-10 02:00:58 +00:00
if russian {
fmt . Printf ( "WARNING! Files *will* be deleted after serving!\n\n" )
}
2023-09-06 22:31:05 +00:00
mux := httprouter . New ( )
index := & Index {
mutex : sync . RWMutex { } ,
list : [ ] string { } ,
}
2023-09-10 16:27:55 +00:00
regexes := & Regexes {
2023-01-27 16:06:10 +00:00
filename : regexp . MustCompile ( ` (.+)([0-9] { 3})(\..+) ` ) ,
2023-09-07 21:20:48 +00:00
alphanumeric : regexp . MustCompile ( ` ^[A-z0-9]*$ ` ) ,
2022-09-16 19:45:54 +00:00
}
2022-09-26 17:31:45 +00:00
2023-09-06 22:31:05 +00:00
srv := & http . Server {
Addr : net . JoinHostPort ( bind , strconv . Itoa ( int ( port ) ) ) ,
Handler : mux ,
IdleTimeout : 10 * time . Minute ,
ReadTimeout : 5 * time . Second ,
WriteTimeout : 5 * time . Minute ,
}
2023-01-25 01:06:15 +00:00
2023-09-06 22:31:05 +00:00
stats := & ServeStats {
2023-01-27 16:06:10 +00:00
mutex : sync . RWMutex { } ,
list : [ ] string { } ,
2023-09-06 22:31:05 +00:00
count : make ( map [ string ] uint32 ) ,
size : make ( map [ string ] string ) ,
times : make ( map [ string ] [ ] string ) ,
2023-01-25 01:06:15 +00:00
}
2023-06-20 15:24:12 +00:00
mux . PanicHandler = serverErrorHandler ( )
2023-05-30 23:47:26 +00:00
2023-09-10 16:27:55 +00:00
mux . GET ( "/" , serveRoot ( paths , regexes , index ) )
2023-09-06 22:31:05 +00:00
mux . GET ( "/favicons/*favicon" , serveFavicons ( ) )
mux . GET ( "/favicon.ico" , serveFavicons ( ) )
2023-09-11 01:29:11 +00:00
mux . GET ( MediaPrefix + "/*media" , serveMedia ( paths , regexes , index ) )
2023-09-06 22:31:05 +00:00
2023-09-10 02:17:17 +00:00
mux . GET ( SourcePrefix + "/*static" , serveStaticFile ( paths , stats , index ) )
2023-09-06 22:31:05 +00:00
mux . GET ( "/version" , serveVersion ( ) )
2023-01-27 19:17:13 +00:00
if cache {
2023-02-05 05:44:31 +00:00
skipIndex := false
2023-02-05 20:34:22 +00:00
if cacheFile != "" {
2023-02-08 13:50:40 +00:00
err := index . Import ( cacheFile )
if err == nil {
2023-02-05 05:44:31 +00:00
skipIndex = true
}
}
if ! skipIndex {
index . generateCache ( args )
}
2023-01-25 01:06:15 +00:00
2023-06-03 18:29:49 +00:00
mux . GET ( "/clear_cache" , serveCacheClear ( args , index ) )
2023-01-25 01:06:15 +00:00
}
2023-04-28 18:19:07 +00:00
if debug {
2023-09-09 05:06:28 +00:00
mux . GET ( "/html/" , serveDebugHtml ( args , index , false ) )
if pageLength != 0 {
mux . GET ( "/html/:page" , serveDebugHtml ( args , index , true ) )
}
2023-06-03 18:29:49 +00:00
mux . GET ( "/json" , serveDebugJson ( args , index ) )
2023-09-09 05:06:28 +00:00
if pageLength != 0 {
mux . GET ( "/json/:page" , serveDebugJson ( args , index ) )
}
2023-04-28 18:19:07 +00:00
}
2023-09-06 15:15:11 +00:00
if profile {
mux . HandlerFunc ( "GET" , "/debug/pprof/" , pprof . Index )
mux . HandlerFunc ( "GET" , "/debug/pprof/cmdline" , pprof . Cmdline )
mux . HandlerFunc ( "GET" , "/debug/pprof/profile" , pprof . Profile )
mux . HandlerFunc ( "GET" , "/debug/pprof/symbol" , pprof . Symbol )
mux . HandlerFunc ( "GET" , "/debug/pprof/trace" , pprof . Trace )
}
2023-09-06 22:31:05 +00:00
if statistics {
if statisticsFile != "" {
stats . Import ( statisticsFile )
gracefulShutdown := make ( chan os . Signal , 1 )
signal . Notify ( gracefulShutdown , syscall . SIGINT , syscall . SIGTERM )
go func ( ) {
<- gracefulShutdown
stats . Export ( statisticsFile )
os . Exit ( 0 )
} ( )
}
mux . GET ( "/stats" , serveStats ( args , stats ) )
2023-09-09 05:06:28 +00:00
if pageLength != 0 {
mux . GET ( "/stats/:page" , serveStats ( args , stats ) )
}
2023-05-31 17:43:07 +00:00
}
err = srv . ListenAndServe ( )
if ! errors . Is ( err , http . ErrServerClosed ) {
2023-01-25 01:06:15 +00:00
return err
}
return nil
2022-09-08 15:12:06 +00:00
}