diff --git a/README.md b/README.md index ccca502d..7a922786 100644 --- a/README.md +++ b/README.md @@ -18,12 +18,20 @@ Simple command line based HTTP file server to share local file system. - Zero third party dependency ## Compile -Minimal required Go version is 1.18. +Minimal required Go version is 1.2. +Ensure this project is located at `GOPATH/src/mjpclab.dev/ghfs`. ```sh go build main.go ``` Will generate executable file "main" in current directory. +If default html template files under `src/tpl` changed, need to re-embed templates into go files: +```bash +cd src +make tpls +``` +Then compile the project like above. + ## Examples Start server on port 8080, root directory is current working directory: ```sh diff --git a/README.zh-CN.md b/README.zh-CN.md index a0f52762..66cf7593 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -18,12 +18,20 @@ - 零第三方依赖 ## 编译 -至少需要Go 1.18版本。 +至少需要Go 1.2版本。 +确保该项目位于 `GOPATH/src/mjpclab.dev/ghfs`。 ```sh go build main.go ``` 会在当前目录生成"main"可执行文件。 +如果修改过`src/tpl`下的默认html模板,需要将其重新嵌入go文件: +```bash +cd src +make tpls +``` +然后再像上面那样编译。 + ## 举例 在8080端口启动服务器,根目录为当前工作目录: ```sh diff --git a/build/build-all-by-docker.sh b/build/build-all-by-docker.sh index 30168d2b..3c9bb617 100755 --- a/build/build-all-by-docker.sh +++ b/build/build-all-by-docker.sh @@ -17,6 +17,7 @@ buildByDocker() { docker run \ --rm \ + --privileged \ -v "$prefix":"$ghfs" \ -e EX_UID="$(id -u)" \ -e EX_GID="$(id -g)" \ @@ -25,7 +26,8 @@ buildByDocker() { if [ -e /etc/apt/sources.list ]; then sed -i -e "s;://[^/ ]*;://mirrors.aliyun.com;" /etc/apt/sources.list; apt-get update; - apt-get install -yq git zip; + apt-get install -yq --force-yes git zip; + dpkg -i '"$ghfs"'/build/pkg/*.deb elif [ -e /etc/apt/sources.list.d/debian.sources ]; then sed -i -e "s;://[^/ ]*;://mirrors.aliyun.com;" /etc/apt/sources.list.d/debian.sources; apt-get update; @@ -42,24 +44,8 @@ buildByDocker() { "$@" } -gover=latest -buildByDocker "$gover" "${builds[@]}" - -#gover=1.24 -#builds=('darwin amd64 -11-big-sur' 'darwin arm64 -11-big-sur') -#buildByDocker "$gover" "${builds[@]}" - -#gover=1.22 -#builds=('darwin amd64 -10.15-catalina' 'darwin arm64 -10.15-catalina') -#buildByDocker "$gover" "${builds[@]}" - -gover=1.20 +gover=1.2 builds=() -builds+=('windows 386 -7-8' 'windows amd64 -7-8') -#builds+=('windows amd64,v2 -7-8' 'windows amd64,v3 -7-8') -#builds+=('darwin amd64 -10.13-high-sierra-10.14-mojave' 'darwin arm64 -10.13-high-sierra-10.14-mojave') +builds+=('windows 386 -2000') +#builds+=("${builds[@]}" 'freebsd 386 -8.x' 'freebsd amd64 -8.x') buildByDocker "$gover" "${builds[@]}" - -#gover=1.16 -#builds=('darwin amd64 -10.12-sierra' 'darwin arm64 -10.12-sierra') -#buildByDocker "$gover" "${builds[@]}" diff --git a/build/build.inc.sh b/build/build.inc.sh index 164869c2..5a325dc6 100755 --- a/build/build.inc.sh +++ b/build/build.inc.sh @@ -4,5 +4,5 @@ MAINNAME='ghfs' MOD=$(go list ../src/) source ./build.inc.version.sh getLdFlags() { - echo "-s -w -X $MOD/version.appVer=$VERSION -X $MOD/version.appArch=${ARCH:-$(go env GOARCH)}" + echo "-s -w" } diff --git a/build/build.sh b/build/build.sh index 05df53bf..a089e6a5 100755 --- a/build/build.sh +++ b/build/build.sh @@ -21,11 +21,23 @@ for build in "$@"; do fi OS_SUFFIX="${arg[2]}" + if [ -n "$GOLANG_VERSION" ]; then # in docker container + cd /usr/src/go/src/ + bash make.bash + cd - + fi; + + cp -r ../src/ /tmp/ + sed -i -e '/var appVer/s/"dev"/"'$VERSION'"/' /tmp/src/version/main.go + sed -i -e '/var appArch/s/runtime.GOARCH/"'$ARCH'"/' /tmp/src/version/main.go + mount --bind /tmp/src ../src + TMP=$(mktemp -d) echo "Building: $GOOS$OS_SUFFIX $ARCH" go build -ldflags "$(getLdFlags)" -o "$TMP/$MAINNAME$(go env GOEXE)" ../main.go cp ../LICENSE "$TMP" + cp ../src/shimgo/LICENSE_GO "$TMP" OUTFILE="$OUTDIR/$MAINNAME-$VERSION-$GOOS$OS_SUFFIX-$GOARCH$ARCH_OPT" if [ "$GOOS" == "windows" ]; then diff --git a/build/pkg/zip_3.0-8_amd64.deb b/build/pkg/zip_3.0-8_amd64.deb new file mode 100644 index 00000000..e20bddc1 Binary files /dev/null and b/build/pkg/zip_3.0-8_amd64.deb differ diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 00000000..6a43f361 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,16 @@ +define embedStr + sed -i -e '/= `/,/`/ { /`/!d }' $(1).go + sed -i -e '/= `/r$(1)' $(1).go + #sed -i -e '/= `/,/`/ { /`/! { /^\s*$$/d; s/^\s\s*// } }' $(1).go +endef + +define embedHex + sed -i -e '/= \[\]byte/,/\}/ { /[{}]/!d }' $(1).go + od -An -tx1 -w16 -v $(1) | sed -e 's/^\s*/\t/g' | sed -E -e 's/[0-9a-fA-F]+/0x&,/g' | sed -i -e '/= \[\]byte/r/dev/stdin' $(1).go +endef + +tpls: + $(call embedStr, tpl/defaultTheme/frontend/index.html) + $(call embedStr, tpl/defaultTheme/frontend/index.css) + $(call embedStr, tpl/defaultTheme/frontend/index.js) + $(call embedHex, tpl/defaultTheme/frontend/favicon.ico) diff --git a/src/app/main.go b/src/app/main.go index 6bfa5d46..2db7cc20 100644 --- a/src/app/main.go +++ b/src/app/main.go @@ -1,14 +1,11 @@ package app import ( - "context" "mjpclab.dev/ghfs/src/goVirtualHost" "mjpclab.dev/ghfs/src/param" "mjpclab.dev/ghfs/src/serverHandler" "mjpclab.dev/ghfs/src/serverLog" "mjpclab.dev/ghfs/src/tpl/theme" - "net/http" - "time" ) type App struct { @@ -18,14 +15,7 @@ type App struct { } func (app *App) Open() []error { - errs := app.vhostSvc.Open() - es := make([]error, 0, len(errs)) - for i := range errs { - if errs[i] != http.ErrServerClosed { - es = append(es, errs[i]) - } - } - return es + return app.vhostSvc.Open() } func (app *App) Close() { @@ -33,14 +23,6 @@ func (app *App) Close() { app.logMan.CloseFiles() } -func (app *App) Shutdown() { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100) - app.vhostSvc.Shutdown(ctx) - cancel() - - app.logMan.CloseFiles() -} - func (app *App) ReOpenLog() []error { return app.logMan.ReOpenFiles() } diff --git a/src/goNixArgParser/helper.go b/src/goNixArgParser/helper.go index 79d0f954..d32b38a5 100644 --- a/src/goNixArgParser/helper.go +++ b/src/goNixArgParser/helper.go @@ -1,7 +1,7 @@ package goNixArgParser import ( - "io" + "io/ioutil" "os" "regexp" ) @@ -79,7 +79,7 @@ func LoadConfigArgs(filename string) (args []string, err error) { defer file.Close() } - bytesConfig, err := io.ReadAll(file) + bytesConfig, err := ioutil.ReadAll(file) if err != nil { return nil, err } diff --git a/src/goNixArgParser/optionSet.go b/src/goNixArgParser/optionSet.go index 311b053c..1949e6ba 100644 --- a/src/goNixArgParser/optionSet.go +++ b/src/goNixArgParser/optionSet.go @@ -3,7 +3,7 @@ package goNixArgParser import ( "errors" "io" - "os" + "mjpclab.dev/ghfs/src/shimgo" "strings" ) @@ -155,11 +155,12 @@ func (s *OptionSet) Add(opt Option) error { } // redundant - env maps + for _, envVar := range option.EnvVars { if len(envVar) == 0 { continue } - envValue, hasEnv := os.LookupEnv(envVar) + envValue, hasEnv := shimgo.Os_LookupEnv(envVar) if !hasEnv { continue } diff --git a/src/goVirtualHost/serveable.go b/src/goVirtualHost/serveable.go index e222a453..806f14d0 100644 --- a/src/goVirtualHost/serveable.go +++ b/src/goVirtualHost/serveable.go @@ -1,8 +1,8 @@ package goVirtualHost import ( - "context" "crypto/tls" + "mjpclab.dev/ghfs/src/shimgo" "net/http" ) @@ -72,6 +72,9 @@ func (serveable *serveable) loadCertificates() (errs []error) { errs = append(errs, es...) } + es := serveable.updateHttpServerTLSConfig() + errs = append(errs, es...) + return } @@ -80,12 +83,17 @@ func (serveable *serveable) updateHttpServerTLSConfig() (errs []error) { return } + certs := make([]tls.Certificate, 0, len(serveable.vhosts)) + for _, vh := range serveable.vhosts { + for _, cert := range vh.loadedCerts { + certs = append(certs, *cert) + } + } + serveable.server.TLSConfig = &tls.Config{ - GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { - vh := serveable.lookupVhost(hello.ServerName) - return vh.lookupCertificate(hello) - }, + Certificates: certs, } + serveable.server.TLSConfig.BuildNameToCertificate() return } @@ -94,22 +102,17 @@ func (serveable *serveable) init() (errs []error) { serveable.updateDefaultVhost() serveable.updateHttpServerHandler() errs = serveable.loadCertificates() - serveable.updateHttpServerTLSConfig() return } func (serveable *serveable) open(l *listenable) error { if serveable.useTLS { - return serveable.server.ServeTLS(l.listener, "", "") + return shimgo.Net_Http_Server_ServeTLS(serveable.server, l.listener, "", "") } else { return serveable.server.Serve(l.listener) } } -func (serveable *serveable) shutdown(ctx context.Context) error { - return serveable.server.Shutdown(ctx) -} - func (serveable *serveable) close() error { - return serveable.server.Close() + return nil } diff --git a/src/goVirtualHost/service.go b/src/goVirtualHost/service.go index ca435cd1..f87b6fe4 100644 --- a/src/goVirtualHost/service.go +++ b/src/goVirtualHost/service.go @@ -1,7 +1,6 @@ package goVirtualHost import ( - "context" "errors" "sync" ) @@ -143,31 +142,6 @@ func (svc *Service) ReloadCertificates() (errs []error) { return } -func (svc *Service) Shutdown(ctx context.Context) { - svc.mu.Lock() - if svc.state >= stateClosed { - svc.mu.Unlock() - return - } - svc.state = stateClosed - svc.mu.Unlock() - - wg := &sync.WaitGroup{} - for _, lis := range svc.listenables { - l := lis - - wg.Add(1) - go func() { - if l.serveable != nil { - l.serveable.shutdown(ctx) - } - l.close() - wg.Done() - }() - } - wg.Wait() -} - func (svc *Service) Close() { svc.mu.Lock() if svc.state >= stateClosed { diff --git a/src/goVirtualHost/util.go b/src/goVirtualHost/util.go index b1b0db72..5ecd69ed 100644 --- a/src/goVirtualHost/util.go +++ b/src/goVirtualHost/util.go @@ -1,6 +1,7 @@ package goVirtualHost import ( + "mjpclab.dev/ghfs/src/shimgo" "net" "sort" "strings" @@ -29,7 +30,7 @@ func extractHostName(host string) string { } } - colonIndex := strings.LastIndexByte(host, ':') + colonIndex := shimgo.Strings_LastIndexByte(host, ':') if colonIndex >= 0 { return host[:colonIndex] } @@ -104,7 +105,7 @@ func splitListen(listen string, useTLS bool) (l43proto, ip, port string) { } colonIndex := strings.IndexByte(listen, ':') - lastColonIndex := strings.LastIndexByte(listen, ':') + lastColonIndex := shimgo.Strings_LastIndexByte(listen, ':') // ipv6 squareEnd := strings.IndexByte(listen, ']') @@ -129,7 +130,7 @@ func splitListen(listen string, useTLS bool) (l43proto, ip, port string) { dotIndex2 := dotIndex1 + 1 + strings.IndexByte(listen[dotIndex1+1:], '.') dotIndex3 := dotIndex2 + 1 + strings.IndexByte(listen[dotIndex2+1:], '.') dotIndex4 := dotIndex3 + 1 + strings.IndexByte(listen[dotIndex3+1:], '.') - lastDotIndex := strings.LastIndexByte(listen, '.') + lastDotIndex := shimgo.Strings_LastIndexByte(listen, '.') isIPv4 := dotIndex1 > 0 && dotIndex2 > dotIndex1 && dotIndex3 > dotIndex2 && dotIndex4 == dotIndex3 && colonIndex == lastColonIndex && (lastColonIndex == -1 || lastColonIndex > lastDotIndex+1) diff --git a/src/goVirtualHost/vhost.go b/src/goVirtualHost/vhost.go index e5f3a09c..7cccf052 100644 --- a/src/goVirtualHost/vhost.go +++ b/src/goVirtualHost/vhost.go @@ -1,9 +1,6 @@ package goVirtualHost import ( - "crypto/tls" - "crypto/x509" - "errors" "net/http" "strings" ) @@ -49,36 +46,3 @@ func (vh *vhost) loadCertificates() []error { return errs } - -func (vh *vhost) lookupCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { - certLen := len(vh.loadedCerts) - if certLen == 1 { - return vh.loadedCerts[0], nil - } - - for _, cert := range vh.loadedCerts { - err := hello.SupportsCertificate(cert) - if err == nil { - return cert, err - } - } - - for _, cert := range vh.loadedCerts { - if cert.Leaf == nil { - cert.Leaf, _ = x509.ParseCertificate(cert.Certificate[0]) - if cert.Leaf == nil { - continue - } - } - err := cert.Leaf.VerifyHostname(hello.ServerName) - if err == nil { - return cert, err - } - } - - if certLen > 0 { - return vh.loadedCerts[0], nil - } - - return nil, errors.New("cannot find proper certificate for " + hello.ServerName) -} diff --git a/src/serverHandler/archive.go b/src/serverHandler/archive.go index 4136705b..e181a1ca 100644 --- a/src/serverHandler/archive.go +++ b/src/serverHandler/archive.go @@ -1,9 +1,9 @@ package serverHandler import ( + "mjpclab.dev/ghfs/src/shimgo" "mjpclab.dev/ghfs/src/util" "net/http" - "net/url" "os" "path" "strings" @@ -52,13 +52,6 @@ func (h *aliasHandler) visitTreeNode( childSelections []string, archiveCallback archiveCallback, ) { - select { - case <-r.Context().Done(): - return - default: - break - } - needAuth, _ := h.needAuth("", urlPath, fsPath) userId, _, err := h.verifyAuth(r, urlPath, fsPath) if needAuth && err != nil { @@ -179,7 +172,7 @@ func (h *aliasHandler) archiveFiles( } func writeArchiveHeader(w http.ResponseWriter, contentType, filename string) { - filename = url.PathEscape(filename) + filename = shimgo.Net_Url_PathEscape(filename) header := w.Header() header.Set("Content-Type", contentType) diff --git a/src/serverHandler/auth.go b/src/serverHandler/auth.go index 0f1a393b..8a58bfd5 100644 --- a/src/serverHandler/auth.go +++ b/src/serverHandler/auth.go @@ -2,6 +2,7 @@ package serverHandler import ( "errors" + "mjpclab.dev/ghfs/src/shimgo" "net/http" "net/url" "strings" @@ -34,7 +35,7 @@ func (h *aliasHandler) notifyAuth(w http.ResponseWriter) { } func (h *aliasHandler) verifyAuth(r *http.Request, vhostReqPath, reqFsPath string) (authUserId int, authUserName string, err error) { - inputUser, inputPass, hasAuthReq := r.BasicAuth() + inputUser, inputPass, hasAuthReq := shimgo.Net_Http_BasicAuth(r) if hasAuthReq { userid, username, success := h.users.Auth(inputUser, inputPass) @@ -60,7 +61,7 @@ func (h *aliasHandler) redirectWithoutRequestAuth(w http.ResponseWriter, r *http index := strings.Index(r.URL.RawQuery, authQueryParam+"=") if index >= 0 { returnUrl = r.URL.RawQuery[index+len(authQueryParam)+1:] - index = strings.LastIndexByte(returnUrl, '&') + index = shimgo.Strings_LastIndexByte(returnUrl, '&') if index >= 0 { returnUrl = returnUrl[:index] } diff --git a/src/serverHandler/content.go b/src/serverHandler/content.go index 5d9dcf55..7e1f0681 100644 --- a/src/serverHandler/content.go +++ b/src/serverHandler/content.go @@ -1,8 +1,8 @@ package serverHandler import ( + "mjpclab.dev/ghfs/src/shimgo" "net/http" - "net/url" ) func (h *aliasHandler) content(w http.ResponseWriter, r *http.Request, session *sessionContext, data *responseData) { @@ -10,7 +10,7 @@ func (h *aliasHandler) content(w http.ResponseWriter, r *http.Request, session * header.Set("Vary", session.vary) header.Set("X-Content-Type-Options", "nosniff") if data.IsDownload { - filename := url.PathEscape(data.ItemName) + filename := shimgo.Net_Url_PathEscape(data.ItemName) header.Set("Content-Disposition", "attachment; filename="+filename+"; filename*=UTF-8''"+filename) } diff --git a/src/serverHandler/log.go b/src/serverHandler/log.go index 0ec83de0..eb11a22e 100644 --- a/src/serverHandler/log.go +++ b/src/serverHandler/log.go @@ -2,9 +2,9 @@ package serverHandler import ( "mjpclab.dev/ghfs/src/serverLog" + "mjpclab.dev/ghfs/src/shimgo" "mjpclab.dev/ghfs/src/util" "net/http" - "net/url" "strconv" ) @@ -24,7 +24,7 @@ func logRequest(logger *serverLog.Logger, r *http.Request, statusCode int) { var unescapedUri []byte unescapedLen := 0 - unescapedStr, err := url.PathUnescape(r.RequestURI) + unescapedStr, err := shimgo.Net_Url_PathUnescape(r.RequestURI) if err == nil && unescapedStr != r.RequestURI { unescapedUri = util.EscapeControllingRune(unescapedStr) if len(unescapedUri) > 0 { diff --git a/src/serverHandler/mutate.go b/src/serverHandler/mutate.go index 6cdb385c..d56acc4e 100644 --- a/src/serverHandler/mutate.go +++ b/src/serverHandler/mutate.go @@ -1,11 +1,12 @@ package serverHandler import ( + "mjpclab.dev/ghfs/src/shimgo" "net/http" ) func (h *aliasHandler) mutate(w http.ResponseWriter, r *http.Request, session *sessionContext, data *responseData) (ok bool) { - if r.Method != http.MethodPost { + if r.Method != shimgo.Net_Http_MethodPost { data.Status = http.StatusMethodNotAllowed return } diff --git a/src/serverHandler/pathTransformHandler.go b/src/serverHandler/pathTransformHandler.go index 240a8b3b..af535594 100644 --- a/src/serverHandler/pathTransformHandler.go +++ b/src/serverHandler/pathTransformHandler.go @@ -28,7 +28,10 @@ func (transformer pathTransformHandler) ServeHTTP(w http.ResponseWriter, r *http } else { urlPathDir = urlPath } - r.URL.RawPath = urlPathDir + r.RequestURI = urlPathDir + if len(r.URL.RawQuery) > 0 { + r.RequestURI += "?" + r.URL.RawQuery + } if len(transformer.prefixes) == 0 { r.URL.Path = urlPathDir diff --git a/src/serverHandler/pathValues.go b/src/serverHandler/pathValues.go index 221196ad..8822e9b6 100644 --- a/src/serverHandler/pathValues.go +++ b/src/serverHandler/pathValues.go @@ -4,26 +4,26 @@ package serverHandler type prefixFilter func(whole, prefix string) bool -// pathValues +// pathInts -type pathValues[T any] struct { +type pathInts struct { path string - values []T + values []int } -type pathValuesList[T any] []pathValues[T] +type pathIntsList []pathInts -func (list pathValuesList[T]) mergePrefixMatched(mergeWith []T, matchPrefix prefixFilter, refPath string) []T { - var result []T +func (list pathIntsList) mergePrefixMatched(mergeWith []int, matchPrefix prefixFilter, refPath string) []int { + var result []int if mergeWith != nil { - result = make([]T, len(mergeWith)) + result = make([]int, len(mergeWith)) copy(result, mergeWith) } for i := range list { if matchPrefix(refPath, list[i].path) { if result == nil { - result = []T{} + result = []int{} } result = append(result, list[i].values...) } @@ -36,8 +36,8 @@ func (list pathValuesList[T]) mergePrefixMatched(mergeWith []T, matchPrefix pref } } -func (list pathValuesList[T]) filterSuccessor(includeSelfPredecessor bool, matchPrefix prefixFilter, refPath string) pathValuesList[T] { - var result pathValuesList[T] +func (list pathIntsList) filterSuccessor(includeSelfPredecessor bool, matchPrefix prefixFilter, refPath string) pathIntsList { + var result pathIntsList for i := range list { if len(list[i].path) <= len(refPath) { @@ -56,20 +56,109 @@ func (list pathValuesList[T]) filterSuccessor(includeSelfPredecessor bool, match } } -// pathInts +// pathStrings -type pathInts = pathValues[int] -type pathIntsList = pathValuesList[int] +type pathStrings struct { + path string + values []string +} -// pathStrings +type pathStringsList []pathStrings + +func (list pathStringsList) mergePrefixMatched(mergeWith []string, matchPrefix prefixFilter, refPath string) []string { + var result []string + if mergeWith != nil { + result = make([]string, len(mergeWith)) + copy(result, mergeWith) + } + + for i := range list { + if matchPrefix(refPath, list[i].path) { + if result == nil { + result = []string{} + } + result = append(result, list[i].values...) + } + } + + if mergeWith != nil && len(mergeWith) == len(result) { + return mergeWith + } else { + return result + } +} -type pathStrings = pathValues[string] -type pathStringsList = pathValuesList[string] +func (list pathStringsList) filterSuccessor(includeSelfPredecessor bool, matchPrefix prefixFilter, refPath string) pathStringsList { + var result pathStringsList + + for i := range list { + if len(list[i].path) <= len(refPath) { + if includeSelfPredecessor && matchPrefix(refPath, list[i].path) { + result = append(result, list[i]) + } + } else if matchPrefix(list[i].path, refPath) { + result = append(result, list[i]) + } + } + + if len(list) == len(result) { + return list + } else { + return result + } +} // pathHeaders -type pathHeaders = pathValues[[2]string] -type pathHeadersList = pathValuesList[[2]string] +type pathHeaders struct { + path string + values [][2]string +} + +type pathHeadersList []pathHeaders + +func (list pathHeadersList) mergePrefixMatched(mergeWith [][2]string, matchPrefix prefixFilter, refPath string) [][2]string { + var result [][2]string + if mergeWith != nil { + result = make([][2]string, len(mergeWith)) + copy(result, mergeWith) + } + + for i := range list { + if matchPrefix(refPath, list[i].path) { + if result == nil { + result = [][2]string{} + } + result = append(result, list[i].values...) + } + } + + if mergeWith != nil && len(mergeWith) == len(result) { + return mergeWith + } else { + return result + } +} + +func (list pathHeadersList) filterSuccessor(includeSelfPredecessor bool, matchPrefix prefixFilter, refPath string) pathHeadersList { + var result pathHeadersList + + for i := range list { + if len(list[i].path) <= len(refPath) { + if includeSelfPredecessor && matchPrefix(refPath, list[i].path) { + result = append(result, list[i]) + } + } else if matchPrefix(list[i].path, refPath) { + result = append(result, list[i]) + } + } + + if len(list) == len(result) { + return list + } else { + return result + } +} // []string diff --git a/src/serverHandler/preprocessHandler.go b/src/serverHandler/preprocessHandler.go index 43364468..2019447b 100644 --- a/src/serverHandler/preprocessHandler.go +++ b/src/serverHandler/preprocessHandler.go @@ -5,6 +5,7 @@ import ( "mjpclab.dev/ghfs/src/serverCompress" "mjpclab.dev/ghfs/src/serverLog" "net/http" + "strings" ) type preprocessHandler struct { @@ -20,9 +21,13 @@ func (pph preprocessHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { rw := serverCompress.NewResponseWriter(w, r) if len(pph.preMiddlewares) > 0 { + prefixReqPath := r.RequestURI // init by pathTransformHandler + if qsIndex := strings.IndexByte(prefixReqPath, '?'); qsIndex >= 0 { + prefixReqPath = prefixReqPath[:qsIndex] + } status := 0 middlewareContext := &middleware.Context{ - PrefixReqPath: r.URL.RawPath, // init by pathTransformHandler + PrefixReqPath: prefixReqPath, VhostReqPath: r.URL.Path, Logger: pph.logger, Status: &status, diff --git a/src/serverHandler/sessionData.go b/src/serverHandler/sessionData.go index 6cf61d6b..3cf6d818 100644 --- a/src/serverHandler/sessionData.go +++ b/src/serverHandler/sessionData.go @@ -4,6 +4,7 @@ import ( "html/template" "mjpclab.dev/ghfs/src/acceptHeaders" "mjpclab.dev/ghfs/src/i18n" + "mjpclab.dev/ghfs/src/shimgo" "mjpclab.dev/ghfs/src/util" "net/http" "os" @@ -348,7 +349,11 @@ func dereferenceSymbolLinks(reqFsPath string, subItems []os.FileInfo) (errs []er func (h *aliasHandler) getSessionData(r *http.Request) (session *sessionContext, data *responseData) { var errs []error - prefixReqPath := r.URL.RawPath // init by pathTransformHandler + prefixReqPath := r.RequestURI // init by pathTransformHandler + if qsIndex := strings.IndexByte(prefixReqPath, '?'); qsIndex >= 0 { + prefixReqPath = prefixReqPath[:qsIndex] + } + vhostReqPath := r.URL.Path tailSlash := vhostReqPath[len(vhostReqPath)-1] == '/' @@ -383,7 +388,7 @@ func (h *aliasHandler) getSessionData(r *http.Request) (session *sessionContext, isSimple = true case strings.HasPrefix(rawQuery, "download"): isDownload = true - case strings.HasPrefix(rawQuery, "upload") && r.Method == http.MethodPost: + case strings.HasPrefix(rawQuery, "upload") && r.Method == shimgo.Net_Http_MethodPost: isUpload = true isMutate = true case strings.HasPrefix(rawQuery, "mkdir"): diff --git a/src/serverHandler/sort.go b/src/serverHandler/sort.go index bc5f51b5..1b9869f3 100644 --- a/src/serverHandler/sort.go +++ b/src/serverHandler/sort.go @@ -1,7 +1,7 @@ package serverHandler import ( - "bytes" + "mjpclab.dev/ghfs/src/shimgo" "mjpclab.dev/ghfs/src/util" "os" "sort" @@ -81,8 +81,8 @@ func (xInfos infosNames) LessFileType(i, j int) (less, ok bool) { bufferI := xInfos.names[i] bufferJ := xInfos.names[j] for { - dotIndexI := bytes.LastIndexByte(bufferI, '.') - dotIndexJ := bytes.LastIndexByte(bufferJ, '.') + dotIndexI := shimgo.Bytes_LastIndexByte(bufferI, '.') + dotIndexJ := shimgo.Bytes_LastIndexByte(bufferJ, '.') if dotIndexI < 0 && dotIndexJ < 0 { break } diff --git a/src/serverHandler/upload.go b/src/serverHandler/upload.go index 87bc3f08..a04a533e 100644 --- a/src/serverHandler/upload.go +++ b/src/serverHandler/upload.go @@ -5,6 +5,7 @@ import ( "io" "mime" "mime/multipart" + "mjpclab.dev/ghfs/src/shimgo" "mjpclab.dev/ghfs/src/util" "net/http" "os" @@ -81,7 +82,7 @@ func (h *aliasHandler) saveUploadFiles(authUserName, fsPrefix string, createDir, continue } - filenameIndex := strings.LastIndexByte(partFilePath, '/') + filenameIndex := shimgo.Strings_LastIndexByte(partFilePath, '/') fsInfix := "" formname := part.FormName() diff --git a/src/serverHandler/util.go b/src/serverHandler/util.go index 38564e39..a9365fb0 100644 --- a/src/serverHandler/util.go +++ b/src/serverHandler/util.go @@ -1,6 +1,7 @@ package serverHandler import ( + "mjpclab.dev/ghfs/src/shimgo" "mjpclab.dev/ghfs/src/user" "mjpclab.dev/ghfs/src/util" "net/http" @@ -53,7 +54,7 @@ func wildcardToRegexp(wildcards []string) (*regexp.Regexp, error) { } func getRedirectCode(r *http.Request) int { - if r.Method == http.MethodPost { + if r.Method == shimgo.Net_Http_MethodPost { return http.StatusTemporaryRedirect } else { return http.StatusMovedPermanently @@ -61,14 +62,14 @@ func getRedirectCode(r *http.Request) int { } func NeedResponseBody(method string) bool { - return method != http.MethodHead && - method != http.MethodOptions && - method != http.MethodConnect && - method != http.MethodTrace + return method != shimgo.Net_Http_MethodHead && + method != shimgo.Net_Http_MethodOptions && + method != shimgo.Net_Http_MethodConnect && + method != shimgo.Net_Http_MethodTrace } func lacksHeader(header http.Header, key string) bool { - return len(header.Values(key)) == 0 + return len(header.Get(key)) == 0 } func getCleanFilePath(requestPath string) (filePath string, ok bool) { diff --git a/src/shimgo/LICENSE_GO b/src/shimgo/LICENSE_GO new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/src/shimgo/LICENSE_GO @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/shimgo/net_http.go b/src/shimgo/net_http.go new file mode 100644 index 00000000..6fe648ae --- /dev/null +++ b/src/shimgo/net_http.go @@ -0,0 +1,75 @@ +package shimgo + +import ( + "crypto/tls" + "encoding/base64" + "net" + "net/http" + "strings" +) + +const ( + Net_Http_MethodGet = "GET" + Net_Http_MethodHead = "HEAD" + Net_Http_MethodPost = "POST" + Net_Http_MethodPut = "PUT" + Net_Http_MethodPatch = "PATCH" // RFC 5789 + Net_Http_MethodDelete = "DELETE" + Net_Http_MethodConnect = "CONNECT" + Net_Http_MethodOptions = "OPTIONS" + Net_Http_MethodTrace = "TRACE" +) + +func Net_Http_Server_ServeTLS(srv *http.Server, l net.Listener, certFile, keyFile string) error { + addr := srv.Addr + if addr == "" { + addr = ":https" + } + config := &tls.Config{} + if srv.TLSConfig != nil { + *config = *srv.TLSConfig + } + if config.NextProtos == nil { + config.NextProtos = []string{"http/1.1"} + } + + if len(config.Certificates) == 0 { + var err error + config.Certificates = make([]tls.Certificate, 1) + config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return err + } + } + + tlsListener := tls.NewListener(l, config) + return srv.Serve(tlsListener) +} + +func Net_Http_BasicAuth(r *http.Request) (username, password string, ok bool) { + auth := r.Header.Get("Authorization") + if auth == "" { + return + } + return net_http_parseBasicAuth(auth) +} + +// parseBasicAuth parses an HTTP Basic Authentication string. +// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). +func net_http_parseBasicAuth(auth string) (username, password string, ok bool) { + const prefix = "Basic " + // Case insensitive prefix match. See Issue 22736. + if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) { + return + } + c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) + if err != nil { + return + } + cs := string(c) + s := strings.IndexByte(cs, ':') + if s < 0 { + return + } + return cs[:s], cs[s+1:], true +} diff --git a/src/shimgo/net_url.go b/src/shimgo/net_url.go new file mode 100644 index 00000000..87eb5b2a --- /dev/null +++ b/src/shimgo/net_url.go @@ -0,0 +1,283 @@ +package shimgo + +import ( + "bytes" + "strconv" +) + +type Net_Url_EscapeError string + +func (e Net_Url_EscapeError) Error() string { + return "invalid URL escape " + strconv.Quote(string(e)) +} + +type Net_Url_InvalidHostError string + +func (e Net_Url_InvalidHostError) Error() string { + return "invalid character " + strconv.Quote(string(e)) + " in host name" +} + +type net_url_encoding int + +const ( + net_url_encodePath net_url_encoding = 1 + iota + net_url_encodePathSegment + net_url_encodeHost + net_url_encodeZone + net_url_encodeUserPassword + net_url_encodeQueryComponent + net_url_encodeFragment +) +const net_url_upperhex = "0123456789ABCDEF" + +func net_url_ishex(c byte) bool { + switch { + case '0' <= c && c <= '9': + return true + case 'a' <= c && c <= 'f': + return true + case 'A' <= c && c <= 'F': + return true + } + return false +} + +func net_url_unhex(c byte) byte { + switch { + case '0' <= c && c <= '9': + return c - '0' + case 'a' <= c && c <= 'f': + return c - 'a' + 10 + case 'A' <= c && c <= 'F': + return c - 'A' + 10 + } + return 0 +} + +func Net_Url_PathEscape(s string) string { + return net_url_escape(s, net_url_encodePathSegment) +} + +func net_url_escape(s string, mode net_url_encoding) string { + spaceCount, hexCount := 0, 0 + for i := 0; i < len(s); i++ { + c := s[i] + if net_url_shouldEscape(c, mode) { + if c == ' ' && mode == net_url_encodeQueryComponent { + spaceCount++ + } else { + hexCount++ + } + } + } + + if spaceCount == 0 && hexCount == 0 { + return s + } + + var buf [64]byte + var t []byte + + required := len(s) + 2*hexCount + if required <= len(buf) { + t = buf[:required] + } else { + t = make([]byte, required) + } + + if hexCount == 0 { + copy(t, s) + for i := 0; i < len(s); i++ { + if s[i] == ' ' { + t[i] = '+' + } + } + return string(t) + } + + j := 0 + for i := 0; i < len(s); i++ { + switch c := s[i]; { + case c == ' ' && mode == net_url_encodeQueryComponent: + t[j] = '+' + j++ + case net_url_shouldEscape(c, mode): + t[j] = '%' + t[j+1] = net_url_upperhex[c>>4] + t[j+2] = net_url_upperhex[c&15] + j += 3 + default: + t[j] = s[i] + j++ + } + } + return string(t) +} + +// Return true if the specified character should be escaped when +// appearing in a URL string, according to RFC 3986. +// +// Please be informed that for now shouldEscape does not check all +// reserved characters correctly. See golang.org/issue/5684. +func net_url_shouldEscape(c byte, mode net_url_encoding) bool { + // §2.3 Unreserved characters (alphanum) + if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { + return false + } + + if mode == net_url_encodeHost || mode == net_url_encodeZone { + // §3.2.2 Host allows + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + // as part of reg-name. + // We add : because we include :port as part of host. + // We add [ ] because we include [ipv6]:port as part of host. + // We add < > because they're the only characters left that + // we could possibly allow, and Parse will reject them if we + // escape them (because hosts can't use %-encoding for + // ASCII bytes). + switch c { + case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', ':', '[', ']', '<', '>', '"': + return false + } + } + + switch c { + case '-', '_', '.', '~': // §2.3 Unreserved characters (mark) + return false + + case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved) + // Different sections of the URL allow a few of + // the reserved characters to appear unescaped. + switch mode { + case net_url_encodePath: // §3.3 + // The RFC allows : @ & = + $ but saves / ; , for assigning + // meaning to individual path segments. This package + // only manipulates the path as a whole, so we allow those + // last three as well. That leaves only ? to escape. + return c == '?' + + case net_url_encodePathSegment: // §3.3 + // The RFC allows : @ & = + $ but saves / ; , for assigning + // meaning to individual path segments. + return c == '/' || c == ';' || c == ',' || c == '?' + + case net_url_encodeUserPassword: // §3.2.1 + // The RFC allows ';', ':', '&', '=', '+', '$', and ',' in + // userinfo, so we must escape only '@', '/', and '?'. + // The parsing of userinfo treats ':' as special so we must escape + // that too. + return c == '@' || c == '/' || c == '?' || c == ':' + + case net_url_encodeQueryComponent: // §3.4 + // The RFC reserves (so we must escape) everything. + return true + + case net_url_encodeFragment: // §4.1 + // The RFC text is silent but the grammar allows + // everything, so escape nothing. + return false + } + } + + if mode == net_url_encodeFragment { + // RFC 3986 §2.2 allows not escaping sub-delims. A subset of sub-delims are + // included in reserved from RFC 2396 §2.2. The remaining sub-delims do not + // need to be escaped. To minimize potential breakage, we apply two restrictions: + // (1) we always escape sub-delims outside of the fragment, and (2) we always + // escape single quote to avoid breaking callers that had previously assumed that + // single quotes would be escaped. See issue #19917. + switch c { + case '!', '(', ')', '*': + return false + } + } + + // Everything else must be escaped. + return true +} + +// PathUnescape does the inverse transformation of PathEscape, +// converting each 3-byte encoded substring of the form "%AB" into the +// hex-decoded byte 0xAB. It returns an error if any % is not followed +// by two hexadecimal digits. +// +// PathUnescape is identical to QueryUnescape except that it does not +// unescape '+' to ' ' (space). +func Net_Url_PathUnescape(s string) (string, error) { + return unescape(s, net_url_encodePathSegment) +} + +// unescape unescapes a string; the mode specifies +// which section of the URL string is being unescaped. +func unescape(s string, mode net_url_encoding) (string, error) { + // Count %, check that they're well-formed. + n := 0 + hasPlus := false + for i := 0; i < len(s); { + switch s[i] { + case '%': + n++ + if i+2 >= len(s) || !net_url_ishex(s[i+1]) || !net_url_ishex(s[i+2]) { + s = s[i:] + if len(s) > 3 { + s = s[:3] + } + return "", Net_Url_EscapeError(s) + } + // Per https://tools.ietf.org/html/rfc3986#page-21 + // in the host component %-encoding can only be used + // for non-ASCII bytes. + // But https://tools.ietf.org/html/rfc6874#section-2 + // introduces %25 being allowed to escape a percent sign + // in IPv6 scoped-address literals. Yay. + if mode == net_url_encodeHost && net_url_unhex(s[i+1]) < 8 && s[i:i+3] != "%25" { + return "", Net_Url_EscapeError(s[i : i+3]) + } + if mode == net_url_encodeZone { + // RFC 6874 says basically "anything goes" for zone identifiers + // and that even non-ASCII can be redundantly escaped, + // but it seems prudent to restrict %-escaped bytes here to those + // that are valid host name bytes in their unescaped form. + // That is, you can use escaping in the zone identifier but not + // to introduce bytes you couldn't just write directly. + // But Windows puts spaces here! Yay. + v := net_url_unhex(s[i+1])<<4 | net_url_unhex(s[i+2]) + if s[i:i+3] != "%25" && v != ' ' && net_url_shouldEscape(v, net_url_encodeHost) { + return "", Net_Url_EscapeError(s[i : i+3]) + } + } + i += 3 + case '+': + hasPlus = mode == net_url_encodeQueryComponent + i++ + default: + if (mode == net_url_encodeHost || mode == net_url_encodeZone) && s[i] < 0x80 && net_url_shouldEscape(s[i], mode) { + return "", Net_Url_InvalidHostError(s[i : i+1]) + } + i++ + } + } + + if n == 0 && !hasPlus { + return s, nil + } + + var t bytes.Buffer + t.Grow(len(s) - 2*n) + for i := 0; i < len(s); i++ { + switch s[i] { + case '%': + t.WriteByte(net_url_unhex(s[i+1])<<4 | net_url_unhex(s[i+2])) + i += 2 + case '+': + if mode == net_url_encodeQueryComponent { + t.WriteByte(' ') + } else { + t.WriteByte('+') + } + default: + t.WriteByte(s[i]) + } + } + return t.String(), nil +} diff --git a/src/shimgo/shim.go b/src/shimgo/shim.go new file mode 100644 index 00000000..7e492b95 --- /dev/null +++ b/src/shimgo/shim.go @@ -0,0 +1,18 @@ +package shimgo + +import ( + "syscall" +) + +func Bytes_LastIndexByte(s []byte, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i + } + } + return -1 +} + +func Os_LookupEnv(key string) (string, bool) { + return syscall.Getenv(key) +} diff --git a/src/shimgo/strings.go b/src/shimgo/strings.go new file mode 100644 index 00000000..ac05e2e2 --- /dev/null +++ b/src/shimgo/strings.go @@ -0,0 +1,10 @@ +package shimgo + +func Strings_LastIndexByte(s string, c byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == c { + return i + } + } + return -1 +} diff --git a/src/shimgo/time.go b/src/shimgo/time.go new file mode 100644 index 00000000..02ef6257 --- /dev/null +++ b/src/shimgo/time.go @@ -0,0 +1,8 @@ +package shimgo + +import "time" + +func Time_AppendFormat(t time.Time, b []byte, layout string) []byte { + formatted := t.Format(layout) + return append(b, []byte(formatted)...) +} diff --git a/src/signal.go b/src/signal.go index 6aa80155..a120962e 100644 --- a/src/signal.go +++ b/src/signal.go @@ -14,7 +14,7 @@ func cleanupOnEnd(appInst *app.App) { go func() { <-chSignal - appInst.Shutdown() + appInst.Close() }() } @@ -23,15 +23,15 @@ func reInitOnHup(appInst *app.App) { signal.Notify(chSignal, syscall.SIGHUP) go func() { - for range chSignal { + for _ = range chSignal { errs := appInst.ReOpenLog() if serverError.CheckError(errs...) { - appInst.Shutdown() + appInst.Close() break } errs = appInst.ReLoadCertificates() if serverError.CheckError(errs...) { - appInst.Shutdown() + appInst.Close() break } } diff --git a/src/tpl/defaultTheme/defaultTheme.go b/src/tpl/defaultTheme/defaultTheme.go index 7e3f206b..f897a5d4 100644 --- a/src/tpl/defaultTheme/defaultTheme.go +++ b/src/tpl/defaultTheme/defaultTheme.go @@ -2,35 +2,24 @@ package defaultTheme import ( "bytes" - _ "embed" + "mjpclab.dev/ghfs/src/tpl/defaultTheme/frontend" "mjpclab.dev/ghfs/src/tpl/theme" + "strings" ) -//go:embed frontend/index.html -var defaultTplStr string - -//go:embed frontend/index.css -var defaultCss []byte - -//go:embed frontend/index.js -var defaultJs []byte - -//go:embed frontend/favicon.ico -var defaultFavicon []byte - var DefaultTheme theme.MemTheme func init() { var err error - DefaultTheme.Template, err = theme.ParsePageTpl(defaultTplStr) + DefaultTheme.Template, err = theme.ParsePageTpl(frontend.DefaultTplStr) if err != nil { DefaultTheme.Template, _ = theme.ParsePageTpl("Builtin Template Error") } DefaultTheme.Assets = theme.Assets{ - {"index.css", "text/css; charset=utf-8", bytes.NewReader(defaultCss)}, - {"index.js", "application/javascript; charset=utf-8", bytes.NewReader(defaultJs)}, - {"favicon.ico", "image/x-icon", bytes.NewReader(defaultFavicon)}, + {"index.css", "text/css; charset=utf-8", strings.NewReader(frontend.DefaultCss)}, + {"index.js", "application/javascript; charset=utf-8", strings.NewReader(frontend.DefaultJs)}, + {"favicon.ico", "image/x-icon", bytes.NewReader(frontend.DefaultFavicon)}, } } diff --git a/src/tpl/defaultTheme/frontend/favicon.ico.go b/src/tpl/defaultTheme/frontend/favicon.ico.go new file mode 100644 index 00000000..47f62b07 --- /dev/null +++ b/src/tpl/defaultTheme/frontend/favicon.ico.go @@ -0,0 +1,341 @@ +package frontend + +var DefaultFavicon = []byte{ + 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x20, 0x20, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0xa8, 0x08, + 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0xc8, 0x06, + 0x00, 0x00, 0xde, 0x08, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x68, 0x05, + 0x00, 0x00, 0xa6, 0x0f, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0x80, 0x80, + 0x80, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, + 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x08, 0x08, + 0x08, 0x00, 0x17, 0x17, 0x17, 0x00, 0x27, 0x27, 0x27, 0x00, 0x37, 0x37, 0x37, 0x00, 0x47, 0x47, + 0x47, 0x00, 0x57, 0x57, 0x57, 0x00, 0x67, 0x67, 0x67, 0x00, 0x77, 0x77, 0x77, 0x00, 0x87, 0x87, + 0x87, 0x00, 0x97, 0x97, 0x97, 0x00, 0xa7, 0xa7, 0xa7, 0x00, 0xb7, 0xb7, 0xb7, 0x00, 0xc7, 0xc7, + 0xc7, 0x00, 0xd7, 0xd7, 0xd7, 0x00, 0xe7, 0xe7, 0xe7, 0x00, 0xf7, 0xf7, 0xf7, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, 0xe5, 0x00, 0x19, 0x19, + 0xff, 0x00, 0x4c, 0x4c, 0xfe, 0x00, 0x7f, 0x7f, 0xff, 0x00, 0xb2, 0xb2, 0xff, 0x00, 0x00, 0x10, + 0x4c, 0x00, 0x00, 0x1b, 0x7f, 0x00, 0x00, 0x26, 0xb2, 0x00, 0x00, 0x31, 0xe5, 0x00, 0x19, 0x4a, + 0xff, 0x00, 0x4c, 0x72, 0xfe, 0x00, 0x7f, 0x9a, 0xff, 0x00, 0xb2, 0xc2, 0xff, 0x00, 0x00, 0x20, + 0x4c, 0x00, 0x00, 0x36, 0x7f, 0x00, 0x00, 0x4c, 0xb2, 0x00, 0x00, 0x62, 0xe5, 0x00, 0x19, 0x7b, + 0xff, 0x00, 0x4c, 0x99, 0xfe, 0x00, 0x7f, 0xb6, 0xff, 0x00, 0xb2, 0xd3, 0xff, 0x00, 0x00, 0x31, + 0x4c, 0x00, 0x00, 0x51, 0x7f, 0x00, 0x00, 0x72, 0xb2, 0x00, 0x00, 0x93, 0xe5, 0x00, 0x19, 0xad, + 0xff, 0x00, 0x4c, 0xbf, 0xfe, 0x00, 0x7f, 0xd1, 0xff, 0x00, 0xb2, 0xe3, 0xff, 0x00, 0x00, 0x41, + 0x4c, 0x00, 0x00, 0x6d, 0x7f, 0x00, 0x00, 0x99, 0xb2, 0x00, 0x00, 0xc4, 0xe5, 0x00, 0x19, 0xde, + 0xff, 0x00, 0x4c, 0xe5, 0xfe, 0x00, 0x7f, 0xec, 0xff, 0x00, 0xb2, 0xf4, 0xff, 0x00, 0x00, 0x4c, + 0x47, 0x00, 0x00, 0x7f, 0x76, 0x00, 0x00, 0xb2, 0xa5, 0x00, 0x00, 0xe5, 0xd5, 0x00, 0x19, 0xff, + 0xee, 0x00, 0x4c, 0xfe, 0xf2, 0x00, 0x7f, 0xff, 0xf5, 0x00, 0xb2, 0xff, 0xf9, 0x00, 0x00, 0x4c, + 0x36, 0x00, 0x00, 0x7f, 0x5b, 0x00, 0x00, 0xb2, 0x7f, 0x00, 0x00, 0xe5, 0xa3, 0x00, 0x19, 0xff, + 0xbd, 0x00, 0x4c, 0xfe, 0xcc, 0x00, 0x7f, 0xff, 0xda, 0x00, 0xb2, 0xff, 0xe9, 0x00, 0x00, 0x4c, + 0x26, 0x00, 0x00, 0x7f, 0x3f, 0x00, 0x00, 0xb2, 0x59, 0x00, 0x00, 0xe5, 0x72, 0x00, 0x19, 0xff, + 0x8c, 0x00, 0x4c, 0xfe, 0xa5, 0x00, 0x7f, 0xff, 0xbf, 0x00, 0xb2, 0xff, 0xd8, 0x00, 0x00, 0x4c, + 0x15, 0x00, 0x00, 0x7f, 0x24, 0x00, 0x00, 0xb2, 0x33, 0x00, 0x00, 0xe5, 0x41, 0x00, 0x19, 0xff, + 0x5b, 0x00, 0x4c, 0xfe, 0x7f, 0x00, 0x7f, 0xff, 0xa3, 0x00, 0xb2, 0xff, 0xc8, 0x00, 0x00, 0x4c, + 0x05, 0x00, 0x00, 0x7f, 0x09, 0x00, 0x00, 0xb2, 0x0c, 0x00, 0x00, 0xe5, 0x10, 0x00, 0x19, 0xff, + 0x29, 0x00, 0x4c, 0xfe, 0x59, 0x00, 0x7f, 0xff, 0x88, 0x00, 0xb2, 0xff, 0xb7, 0x00, 0x0a, 0x4c, + 0x00, 0x00, 0x12, 0x7f, 0x00, 0x00, 0x19, 0xb2, 0x00, 0x00, 0x20, 0xe5, 0x00, 0x00, 0x3a, 0xff, + 0x19, 0x00, 0x66, 0xfe, 0x4c, 0x00, 0x91, 0xff, 0x7f, 0x00, 0xbd, 0xff, 0xb2, 0x00, 0x1b, 0x4c, + 0x00, 0x00, 0x2d, 0x7f, 0x00, 0x00, 0x3f, 0xb2, 0x00, 0x00, 0x51, 0xe5, 0x00, 0x00, 0x6b, 0xff, + 0x19, 0x00, 0x8c, 0xfe, 0x4c, 0x00, 0xad, 0xff, 0x7f, 0x00, 0xcd, 0xff, 0xb2, 0x00, 0x2b, 0x4c, + 0x00, 0x00, 0x48, 0x7f, 0x00, 0x00, 0x65, 0xb2, 0x00, 0x00, 0x83, 0xe5, 0x00, 0x00, 0x9c, 0xff, + 0x19, 0x00, 0xb2, 0xfe, 0x4c, 0x00, 0xc8, 0xff, 0x7f, 0x00, 0xde, 0xff, 0xb2, 0x00, 0x3c, 0x4c, + 0x00, 0x00, 0x64, 0x7f, 0x00, 0x00, 0x8c, 0xb2, 0x00, 0x00, 0xb4, 0xe5, 0x00, 0x00, 0xcd, 0xff, + 0x19, 0x00, 0xd8, 0xfe, 0x4c, 0x00, 0xe3, 0xff, 0x7f, 0x00, 0xee, 0xff, 0xb2, 0x00, 0x4c, 0x4c, + 0x00, 0x00, 0x7f, 0x7f, 0x00, 0x00, 0xb2, 0xb2, 0x00, 0x00, 0xe5, 0xe5, 0x00, 0x00, 0xff, 0xff, + 0x19, 0x00, 0xfe, 0xfe, 0x4c, 0x00, 0xff, 0xff, 0x7f, 0x00, 0xff, 0xff, 0xb2, 0x00, 0x4c, 0x3c, + 0x00, 0x00, 0x7f, 0x64, 0x00, 0x00, 0xb2, 0x8c, 0x00, 0x00, 0xe5, 0xb4, 0x00, 0x00, 0xff, 0xcd, + 0x19, 0x00, 0xfe, 0xd8, 0x4c, 0x00, 0xff, 0xe3, 0x7f, 0x00, 0xff, 0xee, 0xb2, 0x00, 0x4c, 0x2b, + 0x00, 0x00, 0x7f, 0x48, 0x00, 0x00, 0xb2, 0x66, 0x00, 0x00, 0xe5, 0x83, 0x00, 0x00, 0xff, 0x9c, + 0x19, 0x00, 0xfe, 0xb2, 0x4c, 0x00, 0xff, 0xc8, 0x7f, 0x00, 0xff, 0xde, 0xb2, 0x00, 0x4c, 0x1b, + 0x00, 0x00, 0x7f, 0x2d, 0x00, 0x00, 0xb2, 0x3f, 0x00, 0x00, 0xe5, 0x51, 0x00, 0x00, 0xff, 0x6b, + 0x19, 0x00, 0xfe, 0x8c, 0x4c, 0x00, 0xff, 0xad, 0x7f, 0x00, 0xff, 0xcd, 0xb2, 0x00, 0x4c, 0x0a, + 0x00, 0x00, 0x7f, 0x12, 0x00, 0x00, 0xb2, 0x19, 0x00, 0x00, 0xe5, 0x20, 0x00, 0x00, 0xff, 0x3a, + 0x19, 0x00, 0xfe, 0x65, 0x4c, 0x00, 0xff, 0x91, 0x7f, 0x00, 0xff, 0xbd, 0xb2, 0x00, 0x4c, 0x00, + 0x05, 0x00, 0x7f, 0x00, 0x09, 0x00, 0xb2, 0x00, 0x0c, 0x00, 0xe5, 0x00, 0x10, 0x00, 0xff, 0x19, + 0x29, 0x00, 0xfe, 0x4c, 0x59, 0x00, 0xff, 0x7f, 0x88, 0x00, 0xff, 0xb2, 0xb7, 0x00, 0x4c, 0x00, + 0x15, 0x00, 0x7f, 0x00, 0x24, 0x00, 0xb2, 0x00, 0x32, 0x00, 0xe5, 0x00, 0x41, 0x00, 0xff, 0x19, + 0x5b, 0x00, 0xfe, 0x4c, 0x7f, 0x00, 0xff, 0x7f, 0xa3, 0x00, 0xff, 0xb2, 0xc8, 0x00, 0x4c, 0x00, + 0x26, 0x00, 0x7f, 0x00, 0x3f, 0x00, 0xb2, 0x00, 0x59, 0x00, 0xe5, 0x00, 0x72, 0x00, 0xff, 0x19, + 0x8c, 0x00, 0xfe, 0x4c, 0xa5, 0x00, 0xff, 0x7f, 0xbf, 0x00, 0xff, 0xb2, 0xd8, 0x00, 0x4c, 0x00, + 0x36, 0x00, 0x7f, 0x00, 0x5b, 0x00, 0xb2, 0x00, 0x7f, 0x00, 0xe5, 0x00, 0xa3, 0x00, 0xff, 0x19, + 0xbd, 0x00, 0xfe, 0x4c, 0xcc, 0x00, 0xff, 0x7f, 0xda, 0x00, 0xff, 0xb2, 0xe9, 0x00, 0x4c, 0x00, + 0x47, 0x00, 0x7f, 0x00, 0x76, 0x00, 0xb2, 0x00, 0xa5, 0x00, 0xe5, 0x00, 0xd5, 0x00, 0xff, 0x19, + 0xee, 0x00, 0xfe, 0x4c, 0xf2, 0x00, 0xff, 0x7f, 0xf5, 0x00, 0xff, 0xb2, 0xf9, 0x00, 0x41, 0x00, + 0x4c, 0x00, 0x6d, 0x00, 0x7f, 0x00, 0x99, 0x00, 0xb2, 0x00, 0xc4, 0x00, 0xe5, 0x00, 0xde, 0x19, + 0xff, 0x00, 0xe5, 0x4c, 0xfe, 0x00, 0xec, 0x7f, 0xff, 0x00, 0xf4, 0xb2, 0xff, 0x00, 0x31, 0x00, + 0x4c, 0x00, 0x51, 0x00, 0x7f, 0x00, 0x72, 0x00, 0xb2, 0x00, 0x93, 0x00, 0xe5, 0x00, 0xad, 0x19, + 0xff, 0x00, 0xbf, 0x4c, 0xfe, 0x00, 0xd1, 0x7f, 0xff, 0x00, 0xe3, 0xb2, 0xff, 0x00, 0x20, 0x00, + 0x4c, 0x00, 0x36, 0x00, 0x7f, 0x00, 0x4c, 0x00, 0xb2, 0x00, 0x62, 0x00, 0xe5, 0x00, 0x7b, 0x19, + 0xff, 0x00, 0x99, 0x4c, 0xfe, 0x00, 0xb6, 0x7f, 0xff, 0x00, 0xd3, 0xb2, 0xff, 0x00, 0x10, 0x00, + 0x4c, 0x00, 0x1b, 0x00, 0x7f, 0x00, 0x26, 0x00, 0xb2, 0x00, 0x31, 0x00, 0xe5, 0x00, 0x4a, 0x19, + 0xff, 0x00, 0x72, 0x4c, 0xfe, 0x00, 0x9a, 0x7f, 0xff, 0x00, 0xc2, 0xb2, 0xff, 0x00, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x08, + 0x08, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x1d, 0x08, + 0x08, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, + 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, + 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, + 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x08, 0x08, 0x08, 0x00, 0x17, 0x17, 0x17, 0x00, 0x27, 0x27, + 0x27, 0x00, 0x37, 0x37, 0x37, 0x00, 0x47, 0x47, 0x47, 0x00, 0x57, 0x57, 0x57, 0x00, 0x67, 0x67, + 0x67, 0x00, 0x77, 0x77, 0x77, 0x00, 0x87, 0x87, 0x87, 0x00, 0x97, 0x97, 0x97, 0x00, 0xa7, 0xa7, + 0xa7, 0x00, 0xb7, 0xb7, 0xb7, 0x00, 0xc7, 0xc7, 0xc7, 0x00, 0xd7, 0xd7, 0xd7, 0x00, 0xe7, 0xe7, + 0xe7, 0x00, 0xf7, 0xf7, 0xf7, 0x00, 0x00, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0xb2, 0x00, 0x00, 0x00, 0xe5, 0x00, 0x19, 0x19, 0xff, 0x00, 0x4c, 0x4c, 0xfe, 0x00, 0x7f, 0x7f, + 0xff, 0x00, 0xb2, 0xb2, 0xff, 0x00, 0x00, 0x10, 0x4c, 0x00, 0x00, 0x1b, 0x7f, 0x00, 0x00, 0x26, + 0xb2, 0x00, 0x00, 0x31, 0xe5, 0x00, 0x19, 0x4a, 0xff, 0x00, 0x4c, 0x72, 0xfe, 0x00, 0x7f, 0x9a, + 0xff, 0x00, 0xb2, 0xc2, 0xff, 0x00, 0x00, 0x20, 0x4c, 0x00, 0x00, 0x36, 0x7f, 0x00, 0x00, 0x4c, + 0xb2, 0x00, 0x00, 0x62, 0xe5, 0x00, 0x19, 0x7b, 0xff, 0x00, 0x4c, 0x99, 0xfe, 0x00, 0x7f, 0xb6, + 0xff, 0x00, 0xb2, 0xd3, 0xff, 0x00, 0x00, 0x31, 0x4c, 0x00, 0x00, 0x51, 0x7f, 0x00, 0x00, 0x72, + 0xb2, 0x00, 0x00, 0x93, 0xe5, 0x00, 0x19, 0xad, 0xff, 0x00, 0x4c, 0xbf, 0xfe, 0x00, 0x7f, 0xd1, + 0xff, 0x00, 0xb2, 0xe3, 0xff, 0x00, 0x00, 0x41, 0x4c, 0x00, 0x00, 0x6d, 0x7f, 0x00, 0x00, 0x99, + 0xb2, 0x00, 0x00, 0xc4, 0xe5, 0x00, 0x19, 0xde, 0xff, 0x00, 0x4c, 0xe5, 0xfe, 0x00, 0x7f, 0xec, + 0xff, 0x00, 0xb2, 0xf4, 0xff, 0x00, 0x00, 0x4c, 0x47, 0x00, 0x00, 0x7f, 0x76, 0x00, 0x00, 0xb2, + 0xa5, 0x00, 0x00, 0xe5, 0xd5, 0x00, 0x19, 0xff, 0xee, 0x00, 0x4c, 0xfe, 0xf2, 0x00, 0x7f, 0xff, + 0xf5, 0x00, 0xb2, 0xff, 0xf9, 0x00, 0x00, 0x4c, 0x36, 0x00, 0x00, 0x7f, 0x5b, 0x00, 0x00, 0xb2, + 0x7f, 0x00, 0x00, 0xe5, 0xa3, 0x00, 0x19, 0xff, 0xbd, 0x00, 0x4c, 0xfe, 0xcc, 0x00, 0x7f, 0xff, + 0xda, 0x00, 0xb2, 0xff, 0xe9, 0x00, 0x00, 0x4c, 0x26, 0x00, 0x00, 0x7f, 0x3f, 0x00, 0x00, 0xb2, + 0x59, 0x00, 0x00, 0xe5, 0x72, 0x00, 0x19, 0xff, 0x8c, 0x00, 0x4c, 0xfe, 0xa5, 0x00, 0x7f, 0xff, + 0xbf, 0x00, 0xb2, 0xff, 0xd8, 0x00, 0x00, 0x4c, 0x15, 0x00, 0x00, 0x7f, 0x24, 0x00, 0x00, 0xb2, + 0x33, 0x00, 0x00, 0xe5, 0x41, 0x00, 0x19, 0xff, 0x5b, 0x00, 0x4c, 0xfe, 0x7f, 0x00, 0x7f, 0xff, + 0xa3, 0x00, 0xb2, 0xff, 0xc8, 0x00, 0x00, 0x4c, 0x05, 0x00, 0x00, 0x7f, 0x09, 0x00, 0x00, 0xb2, + 0x0c, 0x00, 0x00, 0xe5, 0x10, 0x00, 0x19, 0xff, 0x29, 0x00, 0x4c, 0xfe, 0x59, 0x00, 0x7f, 0xff, + 0x88, 0x00, 0xb2, 0xff, 0xb7, 0x00, 0x0a, 0x4c, 0x00, 0x00, 0x12, 0x7f, 0x00, 0x00, 0x19, 0xb2, + 0x00, 0x00, 0x20, 0xe5, 0x00, 0x00, 0x3a, 0xff, 0x19, 0x00, 0x66, 0xfe, 0x4c, 0x00, 0x91, 0xff, + 0x7f, 0x00, 0xbd, 0xff, 0xb2, 0x00, 0x1b, 0x4c, 0x00, 0x00, 0x2d, 0x7f, 0x00, 0x00, 0x3f, 0xb2, + 0x00, 0x00, 0x51, 0xe5, 0x00, 0x00, 0x6b, 0xff, 0x19, 0x00, 0x8c, 0xfe, 0x4c, 0x00, 0xad, 0xff, + 0x7f, 0x00, 0xcd, 0xff, 0xb2, 0x00, 0x2b, 0x4c, 0x00, 0x00, 0x48, 0x7f, 0x00, 0x00, 0x65, 0xb2, + 0x00, 0x00, 0x83, 0xe5, 0x00, 0x00, 0x9c, 0xff, 0x19, 0x00, 0xb2, 0xfe, 0x4c, 0x00, 0xc8, 0xff, + 0x7f, 0x00, 0xde, 0xff, 0xb2, 0x00, 0x3c, 0x4c, 0x00, 0x00, 0x64, 0x7f, 0x00, 0x00, 0x8c, 0xb2, + 0x00, 0x00, 0xb4, 0xe5, 0x00, 0x00, 0xcd, 0xff, 0x19, 0x00, 0xd8, 0xfe, 0x4c, 0x00, 0xe3, 0xff, + 0x7f, 0x00, 0xee, 0xff, 0xb2, 0x00, 0x4c, 0x4c, 0x00, 0x00, 0x7f, 0x7f, 0x00, 0x00, 0xb2, 0xb2, + 0x00, 0x00, 0xe5, 0xe5, 0x00, 0x00, 0xff, 0xff, 0x19, 0x00, 0xfe, 0xfe, 0x4c, 0x00, 0xff, 0xff, + 0x7f, 0x00, 0xff, 0xff, 0xb2, 0x00, 0x4c, 0x3c, 0x00, 0x00, 0x7f, 0x64, 0x00, 0x00, 0xb2, 0x8c, + 0x00, 0x00, 0xe5, 0xb4, 0x00, 0x00, 0xff, 0xcd, 0x19, 0x00, 0xfe, 0xd8, 0x4c, 0x00, 0xff, 0xe3, + 0x7f, 0x00, 0xff, 0xee, 0xb2, 0x00, 0x4c, 0x2b, 0x00, 0x00, 0x7f, 0x48, 0x00, 0x00, 0xb2, 0x66, + 0x00, 0x00, 0xe5, 0x83, 0x00, 0x00, 0xff, 0x9c, 0x19, 0x00, 0xfe, 0xb2, 0x4c, 0x00, 0xff, 0xc8, + 0x7f, 0x00, 0xff, 0xde, 0xb2, 0x00, 0x4c, 0x1b, 0x00, 0x00, 0x7f, 0x2d, 0x00, 0x00, 0xb2, 0x3f, + 0x00, 0x00, 0xe5, 0x51, 0x00, 0x00, 0xff, 0x6b, 0x19, 0x00, 0xfe, 0x8c, 0x4c, 0x00, 0xff, 0xad, + 0x7f, 0x00, 0xff, 0xcd, 0xb2, 0x00, 0x4c, 0x0a, 0x00, 0x00, 0x7f, 0x12, 0x00, 0x00, 0xb2, 0x19, + 0x00, 0x00, 0xe5, 0x20, 0x00, 0x00, 0xff, 0x3a, 0x19, 0x00, 0xfe, 0x65, 0x4c, 0x00, 0xff, 0x91, + 0x7f, 0x00, 0xff, 0xbd, 0xb2, 0x00, 0x4c, 0x00, 0x05, 0x00, 0x7f, 0x00, 0x09, 0x00, 0xb2, 0x00, + 0x0c, 0x00, 0xe5, 0x00, 0x10, 0x00, 0xff, 0x19, 0x29, 0x00, 0xfe, 0x4c, 0x59, 0x00, 0xff, 0x7f, + 0x88, 0x00, 0xff, 0xb2, 0xb7, 0x00, 0x4c, 0x00, 0x15, 0x00, 0x7f, 0x00, 0x24, 0x00, 0xb2, 0x00, + 0x32, 0x00, 0xe5, 0x00, 0x41, 0x00, 0xff, 0x19, 0x5b, 0x00, 0xfe, 0x4c, 0x7f, 0x00, 0xff, 0x7f, + 0xa3, 0x00, 0xff, 0xb2, 0xc8, 0x00, 0x4c, 0x00, 0x26, 0x00, 0x7f, 0x00, 0x3f, 0x00, 0xb2, 0x00, + 0x59, 0x00, 0xe5, 0x00, 0x72, 0x00, 0xff, 0x19, 0x8c, 0x00, 0xfe, 0x4c, 0xa5, 0x00, 0xff, 0x7f, + 0xbf, 0x00, 0xff, 0xb2, 0xd8, 0x00, 0x4c, 0x00, 0x36, 0x00, 0x7f, 0x00, 0x5b, 0x00, 0xb2, 0x00, + 0x7f, 0x00, 0xe5, 0x00, 0xa3, 0x00, 0xff, 0x19, 0xbd, 0x00, 0xfe, 0x4c, 0xcc, 0x00, 0xff, 0x7f, + 0xda, 0x00, 0xff, 0xb2, 0xe9, 0x00, 0x4c, 0x00, 0x47, 0x00, 0x7f, 0x00, 0x76, 0x00, 0xb2, 0x00, + 0xa5, 0x00, 0xe5, 0x00, 0xd5, 0x00, 0xff, 0x19, 0xee, 0x00, 0xfe, 0x4c, 0xf2, 0x00, 0xff, 0x7f, + 0xf5, 0x00, 0xff, 0xb2, 0xf9, 0x00, 0x41, 0x00, 0x4c, 0x00, 0x6d, 0x00, 0x7f, 0x00, 0x99, 0x00, + 0xb2, 0x00, 0xc4, 0x00, 0xe5, 0x00, 0xde, 0x19, 0xff, 0x00, 0xe5, 0x4c, 0xfe, 0x00, 0xec, 0x7f, + 0xff, 0x00, 0xf4, 0xb2, 0xff, 0x00, 0x31, 0x00, 0x4c, 0x00, 0x51, 0x00, 0x7f, 0x00, 0x72, 0x00, + 0xb2, 0x00, 0x93, 0x00, 0xe5, 0x00, 0xad, 0x19, 0xff, 0x00, 0xbf, 0x4c, 0xfe, 0x00, 0xd1, 0x7f, + 0xff, 0x00, 0xe3, 0xb2, 0xff, 0x00, 0x20, 0x00, 0x4c, 0x00, 0x36, 0x00, 0x7f, 0x00, 0x4c, 0x00, + 0xb2, 0x00, 0x62, 0x00, 0xe5, 0x00, 0x7b, 0x19, 0xff, 0x00, 0x99, 0x4c, 0xfe, 0x00, 0xb6, 0x7f, + 0xff, 0x00, 0xd3, 0xb2, 0xff, 0x00, 0x10, 0x00, 0x4c, 0x00, 0x1b, 0x00, 0x7f, 0x00, 0x26, 0x00, + 0xb2, 0x00, 0x31, 0x00, 0xe5, 0x00, 0x4a, 0x19, 0xff, 0x00, 0x72, 0x4c, 0xfe, 0x00, 0x9a, 0x7f, + 0xff, 0x00, 0xc2, 0xb2, 0xff, 0x00, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x08, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x08, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x08, + 0x08, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x08, 0x08, + 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0x80, 0x80, + 0x80, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, + 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x08, 0x08, + 0x08, 0x00, 0x17, 0x17, 0x17, 0x00, 0x27, 0x27, 0x27, 0x00, 0x37, 0x37, 0x37, 0x00, 0x47, 0x47, + 0x47, 0x00, 0x57, 0x57, 0x57, 0x00, 0x67, 0x67, 0x67, 0x00, 0x77, 0x77, 0x77, 0x00, 0x87, 0x87, + 0x87, 0x00, 0x97, 0x97, 0x97, 0x00, 0xa7, 0xa7, 0xa7, 0x00, 0xb7, 0xb7, 0xb7, 0x00, 0xc7, 0xc7, + 0xc7, 0x00, 0xd7, 0xd7, 0xd7, 0x00, 0xe7, 0xe7, 0xe7, 0x00, 0xf7, 0xf7, 0xf7, 0x00, 0x00, 0x00, + 0x4c, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, 0xe5, 0x00, 0x19, 0x19, + 0xff, 0x00, 0x4c, 0x4c, 0xfe, 0x00, 0x7f, 0x7f, 0xff, 0x00, 0xb2, 0xb2, 0xff, 0x00, 0x00, 0x10, + 0x4c, 0x00, 0x00, 0x1b, 0x7f, 0x00, 0x00, 0x26, 0xb2, 0x00, 0x00, 0x31, 0xe5, 0x00, 0x19, 0x4a, + 0xff, 0x00, 0x4c, 0x72, 0xfe, 0x00, 0x7f, 0x9a, 0xff, 0x00, 0xb2, 0xc2, 0xff, 0x00, 0x00, 0x20, + 0x4c, 0x00, 0x00, 0x36, 0x7f, 0x00, 0x00, 0x4c, 0xb2, 0x00, 0x00, 0x62, 0xe5, 0x00, 0x19, 0x7b, + 0xff, 0x00, 0x4c, 0x99, 0xfe, 0x00, 0x7f, 0xb6, 0xff, 0x00, 0xb2, 0xd3, 0xff, 0x00, 0x00, 0x31, + 0x4c, 0x00, 0x00, 0x51, 0x7f, 0x00, 0x00, 0x72, 0xb2, 0x00, 0x00, 0x93, 0xe5, 0x00, 0x19, 0xad, + 0xff, 0x00, 0x4c, 0xbf, 0xfe, 0x00, 0x7f, 0xd1, 0xff, 0x00, 0xb2, 0xe3, 0xff, 0x00, 0x00, 0x41, + 0x4c, 0x00, 0x00, 0x6d, 0x7f, 0x00, 0x00, 0x99, 0xb2, 0x00, 0x00, 0xc4, 0xe5, 0x00, 0x19, 0xde, + 0xff, 0x00, 0x4c, 0xe5, 0xfe, 0x00, 0x7f, 0xec, 0xff, 0x00, 0xb2, 0xf4, 0xff, 0x00, 0x00, 0x4c, + 0x47, 0x00, 0x00, 0x7f, 0x76, 0x00, 0x00, 0xb2, 0xa5, 0x00, 0x00, 0xe5, 0xd5, 0x00, 0x19, 0xff, + 0xee, 0x00, 0x4c, 0xfe, 0xf2, 0x00, 0x7f, 0xff, 0xf5, 0x00, 0xb2, 0xff, 0xf9, 0x00, 0x00, 0x4c, + 0x36, 0x00, 0x00, 0x7f, 0x5b, 0x00, 0x00, 0xb2, 0x7f, 0x00, 0x00, 0xe5, 0xa3, 0x00, 0x19, 0xff, + 0xbd, 0x00, 0x4c, 0xfe, 0xcc, 0x00, 0x7f, 0xff, 0xda, 0x00, 0xb2, 0xff, 0xe9, 0x00, 0x00, 0x4c, + 0x26, 0x00, 0x00, 0x7f, 0x3f, 0x00, 0x00, 0xb2, 0x59, 0x00, 0x00, 0xe5, 0x72, 0x00, 0x19, 0xff, + 0x8c, 0x00, 0x4c, 0xfe, 0xa5, 0x00, 0x7f, 0xff, 0xbf, 0x00, 0xb2, 0xff, 0xd8, 0x00, 0x00, 0x4c, + 0x15, 0x00, 0x00, 0x7f, 0x24, 0x00, 0x00, 0xb2, 0x33, 0x00, 0x00, 0xe5, 0x41, 0x00, 0x19, 0xff, + 0x5b, 0x00, 0x4c, 0xfe, 0x7f, 0x00, 0x7f, 0xff, 0xa3, 0x00, 0xb2, 0xff, 0xc8, 0x00, 0x00, 0x4c, + 0x05, 0x00, 0x00, 0x7f, 0x09, 0x00, 0x00, 0xb2, 0x0c, 0x00, 0x00, 0xe5, 0x10, 0x00, 0x19, 0xff, + 0x29, 0x00, 0x4c, 0xfe, 0x59, 0x00, 0x7f, 0xff, 0x88, 0x00, 0xb2, 0xff, 0xb7, 0x00, 0x0a, 0x4c, + 0x00, 0x00, 0x12, 0x7f, 0x00, 0x00, 0x19, 0xb2, 0x00, 0x00, 0x20, 0xe5, 0x00, 0x00, 0x3a, 0xff, + 0x19, 0x00, 0x66, 0xfe, 0x4c, 0x00, 0x91, 0xff, 0x7f, 0x00, 0xbd, 0xff, 0xb2, 0x00, 0x1b, 0x4c, + 0x00, 0x00, 0x2d, 0x7f, 0x00, 0x00, 0x3f, 0xb2, 0x00, 0x00, 0x51, 0xe5, 0x00, 0x00, 0x6b, 0xff, + 0x19, 0x00, 0x8c, 0xfe, 0x4c, 0x00, 0xad, 0xff, 0x7f, 0x00, 0xcd, 0xff, 0xb2, 0x00, 0x2b, 0x4c, + 0x00, 0x00, 0x48, 0x7f, 0x00, 0x00, 0x65, 0xb2, 0x00, 0x00, 0x83, 0xe5, 0x00, 0x00, 0x9c, 0xff, + 0x19, 0x00, 0xb2, 0xfe, 0x4c, 0x00, 0xc8, 0xff, 0x7f, 0x00, 0xde, 0xff, 0xb2, 0x00, 0x3c, 0x4c, + 0x00, 0x00, 0x64, 0x7f, 0x00, 0x00, 0x8c, 0xb2, 0x00, 0x00, 0xb4, 0xe5, 0x00, 0x00, 0xcd, 0xff, + 0x19, 0x00, 0xd8, 0xfe, 0x4c, 0x00, 0xe3, 0xff, 0x7f, 0x00, 0xee, 0xff, 0xb2, 0x00, 0x4c, 0x4c, + 0x00, 0x00, 0x7f, 0x7f, 0x00, 0x00, 0xb2, 0xb2, 0x00, 0x00, 0xe5, 0xe5, 0x00, 0x00, 0xff, 0xff, + 0x19, 0x00, 0xfe, 0xfe, 0x4c, 0x00, 0xff, 0xff, 0x7f, 0x00, 0xff, 0xff, 0xb2, 0x00, 0x4c, 0x3c, + 0x00, 0x00, 0x7f, 0x64, 0x00, 0x00, 0xb2, 0x8c, 0x00, 0x00, 0xe5, 0xb4, 0x00, 0x00, 0xff, 0xcd, + 0x19, 0x00, 0xfe, 0xd8, 0x4c, 0x00, 0xff, 0xe3, 0x7f, 0x00, 0xff, 0xee, 0xb2, 0x00, 0x4c, 0x2b, + 0x00, 0x00, 0x7f, 0x48, 0x00, 0x00, 0xb2, 0x66, 0x00, 0x00, 0xe5, 0x83, 0x00, 0x00, 0xff, 0x9c, + 0x19, 0x00, 0xfe, 0xb2, 0x4c, 0x00, 0xff, 0xc8, 0x7f, 0x00, 0xff, 0xde, 0xb2, 0x00, 0x4c, 0x1b, + 0x00, 0x00, 0x7f, 0x2d, 0x00, 0x00, 0xb2, 0x3f, 0x00, 0x00, 0xe5, 0x51, 0x00, 0x00, 0xff, 0x6b, + 0x19, 0x00, 0xfe, 0x8c, 0x4c, 0x00, 0xff, 0xad, 0x7f, 0x00, 0xff, 0xcd, 0xb2, 0x00, 0x4c, 0x0a, + 0x00, 0x00, 0x7f, 0x12, 0x00, 0x00, 0xb2, 0x19, 0x00, 0x00, 0xe5, 0x20, 0x00, 0x00, 0xff, 0x3a, + 0x19, 0x00, 0xfe, 0x65, 0x4c, 0x00, 0xff, 0x91, 0x7f, 0x00, 0xff, 0xbd, 0xb2, 0x00, 0x4c, 0x00, + 0x05, 0x00, 0x7f, 0x00, 0x09, 0x00, 0xb2, 0x00, 0x0c, 0x00, 0xe5, 0x00, 0x10, 0x00, 0xff, 0x19, + 0x29, 0x00, 0xfe, 0x4c, 0x59, 0x00, 0xff, 0x7f, 0x88, 0x00, 0xff, 0xb2, 0xb7, 0x00, 0x4c, 0x00, + 0x15, 0x00, 0x7f, 0x00, 0x24, 0x00, 0xb2, 0x00, 0x32, 0x00, 0xe5, 0x00, 0x41, 0x00, 0xff, 0x19, + 0x5b, 0x00, 0xfe, 0x4c, 0x7f, 0x00, 0xff, 0x7f, 0xa3, 0x00, 0xff, 0xb2, 0xc8, 0x00, 0x4c, 0x00, + 0x26, 0x00, 0x7f, 0x00, 0x3f, 0x00, 0xb2, 0x00, 0x59, 0x00, 0xe5, 0x00, 0x72, 0x00, 0xff, 0x19, + 0x8c, 0x00, 0xfe, 0x4c, 0xa5, 0x00, 0xff, 0x7f, 0xbf, 0x00, 0xff, 0xb2, 0xd8, 0x00, 0x4c, 0x00, + 0x36, 0x00, 0x7f, 0x00, 0x5b, 0x00, 0xb2, 0x00, 0x7f, 0x00, 0xe5, 0x00, 0xa3, 0x00, 0xff, 0x19, + 0xbd, 0x00, 0xfe, 0x4c, 0xcc, 0x00, 0xff, 0x7f, 0xda, 0x00, 0xff, 0xb2, 0xe9, 0x00, 0x4c, 0x00, + 0x47, 0x00, 0x7f, 0x00, 0x76, 0x00, 0xb2, 0x00, 0xa5, 0x00, 0xe5, 0x00, 0xd5, 0x00, 0xff, 0x19, + 0xee, 0x00, 0xfe, 0x4c, 0xf2, 0x00, 0xff, 0x7f, 0xf5, 0x00, 0xff, 0xb2, 0xf9, 0x00, 0x41, 0x00, + 0x4c, 0x00, 0x6d, 0x00, 0x7f, 0x00, 0x99, 0x00, 0xb2, 0x00, 0xc4, 0x00, 0xe5, 0x00, 0xde, 0x19, + 0xff, 0x00, 0xe5, 0x4c, 0xfe, 0x00, 0xec, 0x7f, 0xff, 0x00, 0xf4, 0xb2, 0xff, 0x00, 0x31, 0x00, + 0x4c, 0x00, 0x51, 0x00, 0x7f, 0x00, 0x72, 0x00, 0xb2, 0x00, 0x93, 0x00, 0xe5, 0x00, 0xad, 0x19, + 0xff, 0x00, 0xbf, 0x4c, 0xfe, 0x00, 0xd1, 0x7f, 0xff, 0x00, 0xe3, 0xb2, 0xff, 0x00, 0x20, 0x00, + 0x4c, 0x00, 0x36, 0x00, 0x7f, 0x00, 0x4c, 0x00, 0xb2, 0x00, 0x62, 0x00, 0xe5, 0x00, 0x7b, 0x19, + 0xff, 0x00, 0x99, 0x4c, 0xfe, 0x00, 0xb6, 0x7f, 0xff, 0x00, 0xd3, 0xb2, 0xff, 0x00, 0x10, 0x00, + 0x4c, 0x00, 0x1b, 0x00, 0x7f, 0x00, 0x26, 0x00, 0xb2, 0x00, 0x31, 0x00, 0xe5, 0x00, 0x4a, 0x19, + 0xff, 0x00, 0x72, 0x4c, 0xfe, 0x00, 0x9a, 0x7f, 0xff, 0x00, 0xc2, 0xb2, 0xff, 0x00, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x0f, 0x0f, + 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, + 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x08, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x08, + 0x08, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x08, + 0x08, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, 0x1d, 0x08, 0x08, 0x1d, 0x08, 0x08, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +} diff --git a/src/tpl/defaultTheme/frontend/index.css.go b/src/tpl/defaultTheme/frontend/index.css.go new file mode 100644 index 00000000..75268ef6 --- /dev/null +++ b/src/tpl/defaultTheme/frontend/index.css.go @@ -0,0 +1,665 @@ +package frontend + +const DefaultCss = ` +html, body { + margin: 0; + padding: 0; + background: #fff; +} + +html { + font-family: "roboto_condensedbold", "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +body { + color: #333; + font-size: 0.625em; + font-variant-ligatures: none; + font-variant-numeric: tabular-nums; + font-kerning: none; + -webkit-text-size-adjust: none; + text-size-adjust: none; + hyphens: none; + padding-bottom: 2em; +} + +body, input, textarea, button { + font-family: "Cascadia Mono", Consolas, "Lucida Console", "San Francisco Mono", Menlo, Monaco, "Andale Mono", "DejaVu Sans Mono", "Jetbrains Mono NL", monospace; +} + +input::-ms-clear { + display: none; +} + +form { + margin: 0; + padding: 0; +} + +ul, ol, li { + display: block; + margin: 0; + padding: 0; +} + +a { + display: block; + padding: 0.5em; + color: #000; + text-decoration: none; + outline: 0; +} + +a:hover { + background: #f5f5f5; +} + +a:focus { + background: #fffae0; +} + +a:hover:focus { + background: #faf7ea; +} + +input, button { + min-width: 0; + margin: 0; + padding: 0.25em 0; +} + +input[type=button], +input[type=submit], +input[type=reset], +button { + cursor: pointer; +} + +input:disabled[type=button], +input:disabled[type=submit], +input:disabled[type=reset], +button:disabled { + cursor: default; +} + +input[type=text] { + padding: 0.25em; +} + +em { + font-style: normal; + font-weight: normal; + padding: 0 0.2em; + border: 1px #ddd solid; + border-radius: 3px; +} + +.none, :root body .none { + display: none; +} + +.hidden { + visibility: hidden; +} + + +html::before { + display: none; + content: ''; + position: absolute; + position: fixed; + z-index: 2; + left: 0; + top: 0; + right: 0; + bottom: 0; + opacity: 0.7; + background: #c9c; +} + +html.dragging::before { + display: block; +} + + +.path-list { + font-size: 1.5em; + line-height: 1.2; + overflow: hidden; + border-bottom: 1px #999 solid; + zoom: 1; +} + +.path-list li { + position: relative; + float: left; + text-align: center; + white-space: nowrap; +} + +.path-list a { + display: block; + padding-right: 1.2em; + min-width: 1em; + white-space: pre-wrap; +} + +.path-list a:after { + content: ''; + position: absolute; + top: 50%; + right: 0.5em; + width: 0.4em; + height: 0.4em; + border: 1px solid; + border-color: #ccc #ccc transparent transparent; + -webkit-transform: rotate(45deg) translateY(-50%); + transform: rotate(45deg) translateY(-50%); +} + +.path-list li:last-child a { + padding-right: 0.5em; +} + +.path-list li:last-child a:after { + display: none; +} + +.login { + position: absolute; + z-index: 1; + right: 0; + padding: 0.5em 1em; +} + +.tab { + display: flex; + white-space: nowrap; + margin: 1em 1em -1em 1em; +} + +.tab label { + flex: 0 0 auto; + margin-right: 0.5em; + padding: 1em; + cursor: pointer; +} + +.tab label:focus { + outline: 0; + text-decoration: underline; + text-decoration-style: dotted; +} + +.tab label:hover { + background: #fbfbfb; +} + +.tab label.active { + color: #000; + background: #f7f7f7; +} + +.tab label:last-child { + margin-right: 0; +} + +.panel { + margin: 1em; + padding: 1em; + background: #f7f7f7; +} + +.upload-status { + visibility: hidden; + position: absolute; + position: sticky; + z-index: 1; + left: 0; + top: 0; + width: 100%; + height: 4px; + margin-bottom: -4px; + background: #faf5fa; + background-color: rgba(204, 153, 204, 0.1); + pointer-events: none; +} + +.upload-status.uploading, +.upload-status.failed { + visibility: visible; +} + +.upload-status .label { + position: absolute; + left: 0; + top: 0; + width: 100%; + color: #fff; + text-align: center; + opacity: 0; + transition: transform .2s, opacity .2s; +} + +.upload-status .label .content { + position: relative; + display: inline-block; + vertical-align: top; + text-align: left; + text-align: start; + padding: 0.5em 1em; + box-sizing: border-box; + overflow-wrap: break-word; + word-break: break-word; +} + +.upload-status .info .content { + padding-left: 2.5em; + background: #c9c; + background-color: rgba(204, 153, 204, 0.8); +} + +@keyframes wheel { + from { + transform: rotate(0); + } + to { + transform: rotate(360deg); + } +} + +.upload-status .info .content:before, +.upload-status .info .content:after { + content: ''; + position: absolute; + left: 1em; + top: 0.70em; + width: 1em; + height: 1em; + box-sizing: border-box; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + animation: wheel 1s linear infinite; +} + +.upload-status .info .content:after { + border-color: currentColor transparent transparent transparent; +} + +.upload-status .warn .content { + background: #800000; + background-color: rgba(128, 0, 0, 0.8); +} + +.upload-status.uploading .info, +.upload-status.failed .warn { + opacity: 1; + -webkit-transform: translateY(25%); + transform: translateY(25%); +} + +.upload-status .progress { + position: absolute; + left: 0; + top: 0; + width: 0; + height: 100%; + background: #c9c; +} + +.upload { + position: relative; +} + +.upload input, +.upload button { + display: block; + width: 100%; + box-sizing: border-box; +} + +.upload button { + position: relative; + margin-top: 0.5em; + overflow: hidden; +} + +.upload button span { + position: relative; +} + +.archive { + margin: 1em; + overflow: hidden; + zoom: 1; +} + +.archive a { + position: relative; + float: left; + margin: 0 0.5em; + padding: 1em 1em 1em 3em; + border: 2px #f5f5f5 solid; +} + +.archive a:hover { + border-color: #ddd; +} + +.archive a:before { + content: ''; + position: absolute; + left: 1.1em; + top: 1em; + height: 1em; + width: 3px; + background: #aaa; +} + +.archive a:after { + content: ''; + position: absolute; + left: 0.6em; + top: 1em; + width: 0.5em; + height: 0.5em; + margin-left: 1px; + border: 3px #aaa solid; + border-top-color: transparent; + border-left-color: transparent; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); +} + +.mkdir form { + display: flex; + align-items: center; +} + +.mkdir .name { + flex: 1 1 auto; +} + +.mkdir .submit { + padding-left: 0.5em; + padding-right: 0.5em; +} + +.filter { + display: none; +} + +:root .filter { + display: block; +} + +.filter .form { + position: relative; + display: flex; +} + +.filter input { + flex: 1 1 auto; + width: 97%; + padding-right: 1.5em; + box-sizing: border-box; +} + +.filter button { + display: none; + position: absolute; + right: 0; + top: 0; + bottom: 0; + border: 0; + background: none; + padding: 0 0.5em; +} + +.item-list { + margin: 1em; + line-height: 1.2; +} + +.item-list li { + position: relative; + zoom: 1; +} + +.item-list a { + padding: 0.6em; +} + +.item-list .detail, +.item-list .delete { + display: flex; + flex-flow: row nowrap; + align-items: center; + border-bottom: 1px #f5f5f5 solid; + overflow: hidden; + zoom: 1; +} + +.has-deletable .detail { + padding-right: 2.2em; +} + +.item-list .field { + margin: 0 0 0 1em; + flex-shrink: 0; +} + +.item-list .name { + flex-grow: 1; + flex-shrink: 1; + flex-basis: 0; + margin-left: 0; + font-size: 1.5em; + white-space: pre-wrap; + word-break: break-all; +} + +.item-list .size { + white-space: nowrap; + text-align: right; + color: #666; +} + +.item-list .time { + color: #999; + text-align: right; + white-space: nowrap; + overflow: hidden; +} + +.item-list .delete { + position: absolute; + top: 0; + right: 0; + bottom: 0; + display: flex; + align-items: stretch; +} + +.item-list .delete button { + border: 0; + color: #800000; + background: none; + font-weight: bold; + font-size: 1.6em; + line-height: 1em; + padding: 0.1875em 0.3125em 0.3125em; +} + +.item-list .delete button:hover { + background: #fee; +} + +.item-list .header .detail { + background: #fcfcfc; +} + +.item-list .header .field { + display: inline-block; + margin: 0; + font-size: 1.5em; + color: #808080; + overflow: hidden; +} + +.item-list .header .time { + width: 6.5em; + text-align: center; +} + +.error { + margin: 1em; + padding: 1em; + background: #ffc; +} + +@media only screen and (prefers-color-scheme: light) { + html { + color-scheme: light; + } +} + +@media only screen and (prefers-color-scheme: dark) { + html { + color-scheme: dark; + } + + html, body { + background: #111; + } + + body { + color: #ccc; + } + + a { + color: #ddd; + } + + a:hover { + background-color: #333; + } + + a:focus { + background-color: #330; + } + + a:hover:focus { + background-color: #33331a; + } + + em { + border-color: #555; + } + + .path-list { + border-bottom-color: #999; + } + + .path-list a:after { + border-color: #555 #555 transparent transparent; + } + + .tab label:hover { + background-color: #181818; + } + + .tab label.active { + color: #fff; + background-color: #222; + } + + .panel { + background-color: #222; + } + + .archive a { + border-color: #222; + } + + .archive a:hover { + border-color: #555; + } + + .item-list .detail, + .item-list .delete { + border-bottom-color: #222; + } + + .item-list .size { + color: #999; + } + + .item-list .time { + color: #666; + } + + .item-list .delete button { + color: #f99; + } + + .item-list .delete button:hover { + background-color: #433; + } + + .item-list .header .detail { + background-color: #181818; + } + + .error { + background: #663; + } +} + +@media only screen and (max-width: 375px) { + .item-list .header .time { + width: 4.05em; + } + + .item-list .detail .time span { + display: none; + } +} + +@media only screen and (max-width: 350px) { + .item-list .detail .time { + display: none; + } +} + +@media print { + .panel, .archive { + display: none; + } + + :root .panel { + display: none; + } + + .tab { + display: none; + } + + .item-list li { + page-break-inside: avoid; + break-inside: avoid; + } + + .item-list li.parent { + display: none; + } + + .has-deletable .detail { + padding-right: 0; + } + + .has-deletable .delete { + display: none; + } +} +` diff --git a/src/tpl/defaultTheme/frontend/index.html.go b/src/tpl/defaultTheme/frontend/index.html.go new file mode 100644 index 00000000..36514e22 --- /dev/null +++ b/src/tpl/defaultTheme/frontend/index.html.go @@ -0,0 +1,149 @@ +package frontend + +const DefaultTplStr = ` + + + + + + + + + {{.Path}} + + + + +{{$contextQueryString := .Context.QueryString}} +{{$isSimple := .IsSimple}} +{{$SubItemPrefix := .SubItemPrefix}} +{{if not $isSimple}} +
    + {{range .Paths}} +
  1. {{fmtFilename .Name}}
  2. + {{end}} +
+{{if .LoginAvail}} +{{.Trans.LoginLabel}} +{{else if .AuthUserName}} +[{{.AuthUserName}}] +{{end}} +{{if .CanUpload}} +
+ + {{.Trans.UploadingLabel}} + + + {{.Trans.UploadFailLabel}} + + +
+{{end}} + +{{if .CanMkdir}} +
+
+ + + +
+
+{{end}} + +{{if .CanUpload}} + +
+ + {{if .CanMkdir}} + {{end}} +
+
+
+ + + +
+
+{{end}} + +{{if .CanArchive}} +
+ .tar + .tar.gz + .zip +
+{{end}} + +{{if .CanDelete}} + +{{end}} +{{end}} +{{if .SubItemsHtml}} +
+
+ + +
+
+{{end}} + + +{{if ne .Status 200}}
{{.Status}} +{{if eq .Status 401}} + {{.Trans.Error401}} +{{else if eq .Status 403}} + {{.Trans.Error403}} +{{else if eq .Status 404}} + {{.Trans.Error404}} +{{else}} + {{.Trans.ErrorStatus}}
+{{end}} +{{end}} + + + + +` diff --git a/src/tpl/defaultTheme/frontend/index.js.go b/src/tpl/defaultTheme/frontend/index.js.go new file mode 100644 index 00000000..0dc8fc70 --- /dev/null +++ b/src/tpl/defaultTheme/frontend/index.js.go @@ -0,0 +1,1354 @@ +package frontend + +const DefaultJs = ` +(function () { + var strUndef = 'undefined'; + var protoHttps = 'https:'; + + var classNone = 'none'; + var classHeader = 'header'; + + var selectorNone = '.' + classNone; + var selectorNotNone = ':not(' + selectorNone + ')'; + var selectorItem = '.item-list > li:not(.' + classHeader + '):not(.parent)'; + var selectorItemNone = selectorItem + selectorNone; + var selectorItemNotNone = selectorItem + selectorNotNone; + + var leavingEvent = typeof window.onpagehide !== strUndef ? 'pagehide' : 'beforeunload'; + + var Enter = 'Enter'; + var Escape = 'Escape'; + var Esc = 'Esc'; + var Space = ' '; + + var noop = function () { + }; + + var logError; + if (typeof console !== strUndef) { + logError = function (err) { + console.error(err); + } + } else { + logError = noop; + } + + var hasClass, addClass, removeClass; + if (document.body.classList) { + hasClass = function (el, className) { + return el && el.classList.contains(className); + } + addClass = function (el, className) { + el && el.classList.add(className); + } + removeClass = function (el, className) { + el && el.classList.remove(className); + } + } else { + hasClass = function (el, className) { + if (!el) return; + var reClassName = new RegExp('\\b' + className + '\\b'); + return reClassName.test(el.className); + } + addClass = function (el, className) { + if (!el) return; + var originalClassName = el.className; + var reClassName = new RegExp('\\b' + className + '\\b'); + if (!reClassName.test(originalClassName)) { + el.className = originalClassName + ' ' + className; + } + } + removeClass = function (el, className) { + if (!el) return; + var originalClassName = el.className; + var reClassName = new RegExp('^\\s*' + className + '\\s+|\\s+' + className + '\\b', 'g'); + var newClassName = originalClassName.replace(reClassName, ''); + if (originalClassName !== newClassName) { + el.className = newClassName; + } + } + } + + var hasStorage = false; + try { + if (typeof sessionStorage !== strUndef) hasStorage = true; + } catch (err) { + } + + var lastFocused; + + function enableFilter() { + if (!document.querySelector) { + var filter = document.getElementById && document.getElementById('panel-filter'); + if (filter) { + filter.className += ' none'; + } + return; + } + + // pre check + var filter = document.body.querySelector('.filter'); + if (!filter) { + return; + } + if (!filter.addEventListener) { + filter.className += ' none'; + return; + } + + var input = filter.querySelector('input'); + if (!input) { + return; + } + + var trim = String.prototype.trim ? function (input) { + return input.trim(); + } : function () { + var reEdgeSpaces = /^\s+|\s+$/g + return function (input) { + return input.replace(reEdgeSpaces, ''); + } + }(); + + var clear = filter.querySelector('button'); + + // event handler + var timeoutId; + var lastFilterText = ''; + var doFilter = function () { + var filterText = trim(input.value).toLowerCase(); + if (filterText === lastFilterText) { + return; + } + + var selector, items, i; + + if (!filterText) { // filter cleared, show all items + if (clear) { + clear.style.display = ''; + } + selector = selectorItemNone; + items = document.body.querySelectorAll(selector); + for (i = items.length - 1; i >= 0; i--) { + removeClass(items[i], classNone); + } + } else { + if (clear) { + clear.style.display = 'block'; + } + if (filterText.indexOf(lastFilterText) >= 0) { // increment search, find in visible items + selector = selectorItemNotNone; + } else if (lastFilterText.indexOf(filterText) >= 0) { // decrement search, find in hidden items + selector = selectorItemNone; + } else { + selector = selectorItem; + } + + items = document.body.querySelectorAll(selector); + for (i = items.length - 1; i >= 0; i--) { + var item = items[i]; + var name = item.querySelector('.name'); + if (name && name.textContent.toLowerCase().indexOf(filterText) < 0) { + addClass(item, classNone); + } else { + removeClass(item, classNone); + } + } + } + + lastFilterText = filterText; + }; + + var onValueMayChange = function () { + clearTimeout(timeoutId); + timeoutId = setTimeout(doFilter, 350); + }; + input.addEventListener('input', onValueMayChange, false); + input.addEventListener('change', onValueMayChange, false); + + var onEnter = function () { + clearTimeout(timeoutId); + input.blur(); + doFilter(); + }; + var onEscape = function () { + clearTimeout(timeoutId); + input.value = ''; + doFilter(); + }; + + var ENTER_CODE = 13; + var ESCAPE_CODE = 27; + + input.addEventListener('keydown', function (e) { + if (e.key) { + switch (e.key) { + case Enter: + onEnter(); + e.preventDefault(); + break; + case Escape: + case Esc: + onEscape(); + e.preventDefault(); + break; + } + } else if (e.keyCode) { + switch (e.keyCode) { + case ENTER_CODE: + onEnter(); + e.preventDefault(); + break; + case ESCAPE_CODE: + onEscape(); + e.preventDefault(); + break; + } + } + }, false); + + clear && clear.addEventListener('click', function () { + clearTimeout(timeoutId); + input.value = ''; + input.focus(); + doFilter(); + }); + + // init + if (hasStorage) { + var prevSessionFilter = sessionStorage.getItem(location.pathname); + sessionStorage.removeItem(location.pathname); + + window.addEventListener(leavingEvent, function () { + if (input.value) { + sessionStorage.setItem(location.pathname, input.value); + } + }, false); + + if (prevSessionFilter) { + input.value = prevSessionFilter; + } + } + if (input.value) { + doFilter(); + } + } + + function keepFocusOnBackwardForward() { + if (window.onpageshow === undefined || !document.querySelector) return; + + function onFocus(e) { + var link = e.target; + while (link && !(link instanceof HTMLAnchorElement)) { + link = link.parentElement; + } + if (!link || link === lastFocused) return; + lastFocused = link; + } + + var itemList = document.body.querySelector('.item-list'); + itemList.addEventListener('focusin', onFocus); + itemList.addEventListener('click', onFocus); + window.addEventListener('pageshow', function () { + if (lastFocused && lastFocused !== document.activeElement) { + lastFocused.focus(); + lastFocused.scrollIntoView({block: 'center'}); + } + }); + } + + function focusChildOnNavUp() { + if (!document.querySelector) return; + + function extractCleanUrl(url) { + var sepIndex = url.indexOf('?'); + if (sepIndex < 0) sepIndex = url.indexOf('#'); + if (sepIndex >= 0) { + url = url.substring(0, sepIndex); + } + return url; + } + + var prevUrl = document.referrer; + if (!prevUrl) return; + prevUrl = extractCleanUrl(prevUrl); + + var currUrl = extractCleanUrl(location.href); + + if (prevUrl.length <= currUrl.length) return; + if (prevUrl.substring(0, currUrl.length) !== currUrl) return; + var goesUp = prevUrl.substring(currUrl.length); + if (currUrl[currUrl.length - 1] !== '/' && goesUp[0] !== '/') return; + var matchInfo = /[^/]+/.exec(goesUp); + if (!matchInfo) return; + var prevChildName = matchInfo[0]; + if (!prevChildName) return; + prevChildName = decodeURIComponent(prevChildName); + + var items = document.body.querySelectorAll(selectorItem); + items = Array.prototype.slice.call(items); + var selectorName = '.field.name'; + var selectorLink = 'a'; + for (var i = 0, len = items.length; i < len; i++) { + var item = items[i]; + var elName = item.querySelector(selectorName); + if (!elName) continue; + var text = elName.textContent; + if (text[text.length - 1] === '/') { + text = text.substring(0, text.length - 1); + } + if (text !== prevChildName) continue; + var elLink = item.querySelector(selectorLink); + if (elLink) { + lastFocused = elLink; + elLink.focus(); + elLink.scrollIntoView({block: 'center'}); + } + break; + } + } + + function enableKeyboardNavigate() { + if ( + !document.querySelector || + !document.addEventListener || + !document.body.parentElement + ) { + return; + } + + var pathList = document.body.querySelector('.path-list'); + var itemList = document.body.querySelector('.item-list'); + if (!pathList && !itemList) { + return; + } + + function getFocusableSibling(container, isBackward, startA) { + if (!container) { + return + } + if (!startA) { + startA = container.querySelector(':focus'); + } + var startLI = startA; + while (startLI && startLI.tagName !== 'LI') { + startLI = startLI.parentElement; + } + if (!startLI) { + if (isBackward) { + startLI = container.firstElementChild; + } else { + startLI = container.lastElementChild; + } + } + if (!startLI) { + return; + } + + var siblingLI = startLI; + do { + if (isBackward) { + siblingLI = siblingLI.previousElementSibling; + if (!siblingLI) { + siblingLI = container.lastElementChild; + } + } else { + siblingLI = siblingLI.nextElementSibling; + if (!siblingLI) { + siblingLI = container.firstElementChild; + } + } + } while (siblingLI !== startLI && ( + hasClass(siblingLI, classNone) || + hasClass(siblingLI, classHeader) + )); + + if (siblingLI) { + var siblingA = siblingLI.querySelector('a'); + return siblingA; + } + } + + var selectorFirstAvailLi = 'li:not(.' + classNone + '):not(.' + classHeader + ')'; + + function getFirstFocusableSibling(container) { + var li = container.querySelector(selectorFirstAvailLi); + var a = li && li.querySelector('a'); + return a; + } + + function getLastFocusableSibling(container) { + var a = container.querySelector('li a'); + a = getFocusableSibling(container, true, a); + return a; + } + + function getMatchedFocusableSibling(container, isBackward, startA, buf) { + var skipRound = buf.length === 1; // find next prefix + var matchKeyA; + var firstCheckA; + var secondCheckA; + var a = startA; + do { + if (skipRound) { + skipRound = false; + continue; + } + if (!a) { + continue; + } + + // firstCheckA maybe a focused a that not belongs to the list + // secondCheckA must be in the list + if (!firstCheckA) { + firstCheckA = a; + } else if (firstCheckA === a) { + return; + } else if (!secondCheckA) { + secondCheckA = a; + } else if (secondCheckA === a) { + return; + } + + var textContent = (a.querySelector('.name') || a).textContent.toLowerCase(); + if (buf.length <= textContent.length && textContent.substring(0, buf.length) === buf) { + return a; + } + } while (a = getFocusableSibling(container, isBackward, a)); + return matchKeyA; + } + + var UP = 'Up'; + var DOWN = 'Down'; + var LEFT = 'Left'; + var RIGHT = 'Right'; + + var ARROW_UP = 'ArrowUp'; + var ARROW_DOWN = 'ArrowDown'; + var ARROW_LEFT = 'ArrowLeft'; + var ARROW_RIGHT = 'ArrowRight'; + + var ARROW_UP_CODE = 38; + var ARROW_DOWN_CODE = 40; + var ARROW_LEFT_CODE = 37; + var ARROW_RIGHT_CODE = 39; + + var SKIP_TAGS = ['INPUT', 'BUTTON', 'TEXTAREA']; + + var PLATFORM = navigator.platform; + var IS_MAC_PLATFORM = PLATFORM.indexOf('Mac') >= 0 || PLATFORM.indexOf('iPhone') >= 0 || PLATFORM.indexOf('iPad') >= 0 || PLATFORM.indexOf('iPod') >= 0 + + var lookupKey; + var lookupBuffer; + var lookupStartA; + var lookupTimer; + + function clearLookupContext() { + lookupKey = undefined; + lookupBuffer = ''; + lookupStartA = null; + } + + clearLookupContext(); + + function delayClearLookupContext() { + clearTimeout(lookupTimer); + lookupTimer = setTimeout(clearLookupContext, 850); + } + + function lookup(container, key, isBackward) { + key = key.toLowerCase(); + + var currentLookupStartA; + if (key === lookupKey) { + // same as last key, lookup next for the same key as prefix + currentLookupStartA = container.querySelector(':focus'); + } else { + if (!lookupStartA) { + lookupStartA = container.querySelector(':focus'); + } + currentLookupStartA = lookupStartA; + if (lookupKey === undefined) { + lookupKey = key; + } else { + // key changed, no more prefix match + lookupKey = ''; + } + lookupBuffer += key; + } + delayClearLookupContext(); + return getMatchedFocusableSibling(container, isBackward, currentLookupStartA, lookupKey || lookupBuffer); + } + + var canArrowMove; + var isToEnd; + if (IS_MAC_PLATFORM) { + canArrowMove = function (e) { + return !(e.ctrlKey || e.shiftKey || e.metaKey); // only allow Opt + } + isToEnd = function (e) { + return e.altKey; // Opt key + } + } else { + canArrowMove = function (e) { + return !(e.altKey || e.shiftKey || e.metaKey); // only allow Ctrl + } + isToEnd = function (e) { + return e.ctrlKey; + } + } + + function getFocusItemByKeyPress(e) { + if (SKIP_TAGS.indexOf(e.target.tagName) >= 0) { + return; + } + + if (e.key) { + if (canArrowMove(e)) { + switch (e.key) { + case LEFT: + case ARROW_LEFT: + if (isToEnd(e)) { + return getFirstFocusableSibling(pathList); + } else { + return getFocusableSibling(pathList, true); + } + case RIGHT: + case ARROW_RIGHT: + if (isToEnd(e)) { + return getLastFocusableSibling(pathList); + } else { + return getFocusableSibling(pathList, false); + } + case UP: + case ARROW_UP: + if (isToEnd(e)) { + return getFirstFocusableSibling(itemList); + } else { + return getFocusableSibling(itemList, true); + } + case DOWN: + case ARROW_DOWN: + if (isToEnd(e)) { + return getLastFocusableSibling(itemList); + } else { + return getFocusableSibling(itemList, false); + } + } + } + if (!e.ctrlKey && (!e.altKey || IS_MAC_PLATFORM) && !e.metaKey && e.key.length === 1) { + return lookup(itemList, e.key, e.shiftKey); + } + } else if (e.keyCode) { + if (canArrowMove(e)) { + switch (e.keyCode) { + case ARROW_LEFT_CODE: + if (isToEnd(e)) { + return getFirstFocusableSibling(pathList); + } else { + return getFocusableSibling(pathList, true); + } + case ARROW_RIGHT_CODE: + if (isToEnd(e)) { + return getLastFocusableSibling(pathList); + } else { + return getFocusableSibling(pathList, false); + } + case ARROW_UP_CODE: + if (isToEnd(e)) { + return getFirstFocusableSibling(itemList); + } else { + return getFocusableSibling(itemList, true); + } + case ARROW_DOWN_CODE: + if (isToEnd(e)) { + return getLastFocusableSibling(itemList); + } else { + return getFocusableSibling(itemList, false); + } + } + } + if (!e.ctrlKey && (!e.altKey || IS_MAC_PLATFORM) && !e.metaKey && e.keyCode >= 32 && e.keyCode <= 126) { + return lookup(itemList, String.fromCharCode(e.keyCode), e.shiftKey); + } + } + } + + document.addEventListener('keydown', function (e) { + var newFocusEl = getFocusItemByKeyPress(e); + if (newFocusEl) { + e.preventDefault(); + newFocusEl.focus(); + } + }); + } + + function enhanceUpload() { + if (!document.querySelector || !document.addEventListener) { + return; + } + + var upload = document.body.querySelector('.upload'); + if (!upload) { + return; + } + var form = upload.querySelector('form'); + if (!form) { + return; + } + var fileInput = form.querySelector('.file'); + if (!fileInput) { + return; + } + + var uploadType = document.body.querySelector('.upload-type'); + if (!uploadType) { + return; + } + + var file = 'file'; + var dirFile = 'dirfile'; + var innerDirFile = 'innerdirfile'; + + var optFile = uploadType.querySelector('.' + file); + var optDirFile = uploadType.querySelector('.' + dirFile); + var optInnerDirFile = uploadType.querySelector('.' + innerDirFile); + var optActive = optFile; + var canMkdir = Boolean(optDirFile); + + var padStart = String.prototype.padStart ? function (sourceString, targetLength, padTemplate) { + return sourceString.padStart(targetLength, padTemplate); + } : function (sourceString, targetLength, padTemplate) { + var sourceLength = sourceString.length; + if (sourceLength >= targetLength) { + return sourceString; + } + var padLength = targetLength - sourceLength + var repeatCount = Math.ceil(padLength / padTemplate.length); + var padString; + if (String.prototype.repeat) { + padString = padTemplate.repeat(repeatCount); + } else { + padString = ''; + for (var i = 0; i < repeatCount; i++) { + padString += padTemplate; + } + } + if (padString.length > padLength) { + padString = padString.substring(0, padLength); + } + + return padString + sourceString; + } + + function getTimeStamp() { + var now = new Date(); + var date = String(now.getFullYear() * 10000 + (now.getMonth() + 1) * 100 + now.getDate()); + var time = String(now.getHours() * 10000 + now.getMinutes() * 100 + now.getSeconds()); + var ms = String(now.getMilliseconds()); + date = padStart(date, 8, '0'); + time = padStart(time, 6, '0'); + ms = padStart(ms, 3, '0'); + var ts = '-' + date + '-' + time + '-' + ms; + return ts; + } + + var itemsToFiles; + if (location.protocol === protoHttps && typeof FileSystemHandle !== strUndef && !DataTransferItem.prototype.webkitGetAsEntry) { + var handleKindFile = 'file'; + var handleKindDir = 'directory'; + var permDescriptor = {mode: 'read'}; + itemsToFiles = function (dataTransferItems, canMkdir, onDone, onLacksMkdir) { + function resultsToFiles(results, files, dirPath) { + return Promise.all(results.map(function (result) { + var handle = result.value; + if (handle.kind === handleKindFile) { + return handle.queryPermission(permDescriptor).then(function (queryResult) { + if (queryResult === 'prompt') return handle.requestPermission(permDescriptor); + }).then(function () { + return handle.getFile(); + }).then(function (file) { + var relativePath = dirPath + file.name; + files.push({file: file, relativePath: relativePath}); + })['catch'](function (err) { // workaround IE8- syntax error for ".catch"(reserved keyword) + logError(err); + }); + } else if (handle.kind === handleKindDir) { + return new Promise(function (resolve) { + var childResults = []; + var childIter = handle.values(); + + function onLevelDone() { + childResults = null; + childIter = null; + resolve(); + } + + function addChildResult() { + childIter.next().then(function (result) { + if (result.done) { + if (childResults.length) { + resultsToFiles(childResults, files, dirPath + handle.name + '/').then(onLevelDone); + } else onLevelDone(); + } else { + childResults.push(result); + addChildResult(); + } + }); + } + + addChildResult(); + }); + } + })); + } + + var files = []; + var hasDir = false; + if (!dataTransferItems || !dataTransferItems.length) return onDone(files, hasDir); + + var items = Array.prototype.slice.call(dataTransferItems); + Promise.all(items.map(function (item) { + return item.getAsFileSystemHandle(); + })).then(function (handles) { + handles = handles.filter(Boolean); // undefined for pasted content + hasDir = handles.some(function (handle) { + return handle.kind === handleKindDir; + }); + if (hasDir && !canMkdir) { + return onLacksMkdir(); + } + var handleResults = handles.map(function (handle) { + return {value: handle, done: false}; + }); + resultsToFiles(handleResults, files, '').then(function () { + onDone(files, hasDir); + }); + }); + } + } else { + itemsToFiles = function (dataTransferItems, canMkdir, onDone, onLacksMkdir) { + function entriesToFiles(entries, files, onLevelDone) { + var len = entries.length; + var cb = 0; + if (!len) return onLevelDone(); + + function increaseCb() { + cb++; + if (cb === len) { + onLevelDone(); + } + } + + function dirReaderToFiles(dirReader, files, onAllRead) { + dirReader.readEntries(function (subEntries) { + if (!subEntries.length) return onAllRead(); + entriesToFiles(subEntries, files, function () { + dirReaderToFiles(dirReader, files, onAllRead); + }); + }, onAllRead); + } + + entries.forEach(function (entry) { + if (entry.isFile) { + var relativePath = entry.fullPath; + if (relativePath[0] === '/') { + relativePath = relativePath.substring(1); + } + entry.file(function (file) { + files.push({file: file, relativePath: relativePath}); + increaseCb(); + }, function (err) { + increaseCb(); + logError(err); + }); + } else if (entry.isDirectory) { + var dirReader = entry.createReader(); + dirReaderToFiles(dirReader, files, increaseCb); + } + }); + } + + var files = []; + var hasDir = false; + if (!dataTransferItems || !dataTransferItems.length || !dataTransferItems[0].webkitGetAsEntry) return onDone(files, hasDir); + + var entries = []; + for (var i = 0, len = dataTransferItems.length; i < len; i++) { + var item = dataTransferItems[i]; + var entry = item.webkitGetAsEntry(); + if (!entry) { // undefined for pasted text + continue; + } + if (entry.isFile) { + // Safari cannot get file from entry by entry.file(), if it is a pasted image + // so workaround is for all browsers, just get first hierarchy of files by item.getAsFile() + var file = item.getAsFile(); + files.push({file: file, relativePath: file.name}); + } else if (entry.isDirectory) { + hasDir = true; + if (canMkdir) { + entries.push(entry); + } else { + return onLacksMkdir(); + } + } + } + + entriesToFiles(entries, files, function () { + onDone(files, hasDir); + }); + } + } + + function dataTransferToFiles(dataTransfer, canMkdir, onDone, onLacksMkdir) { + itemsToFiles(dataTransfer.items, canMkdir, function (files, hasDir) { + // ancient Browser + if (files.length === 0 && dataTransfer.files && dataTransfer.files.length) { + files = Array.prototype.slice.call(dataTransfer.files); + } + onDone(files, hasDir); + }, onLacksMkdir); + } + + var switchToFileMode = noop; + var switchToDirMode = noop; + + function enableAddDirFile() { + var classHidden = 'hidden'; + var classActive = 'active'; + + function onClickOpt(optTarget, clearInput) { + if (optTarget === optActive) { + return; + } + removeClass(optActive, classActive); + + optActive = optTarget; + addClass(optActive, classActive); + + if (clearInput) { + fileInput.value = ''; + } + return true; + } + + function onClickOptFile(e) { + if (onClickOpt(optFile, Boolean(e))) { + fileInput.name = file; + fileInput.webkitdirectory = false; + } + } + + function onClickOptDirFile() { + if (onClickOpt(optDirFile, optActive === optFile)) { + fileInput.name = dirFile; + fileInput.webkitdirectory = true; + } + } + + function onClickOptInnerDirFile() { + if (onClickOpt(optInnerDirFile, optActive === optFile)) { + fileInput.name = innerDirFile; + fileInput.webkitdirectory = true; + } + } + + function onKeydownOpt(e) { + switch (e.key) { + case Enter: + case Space: + if (e.ctrlKey || e.altKey || e.metaKey || e.shiftKey) { + break; + } + e.preventDefault(); + e.stopPropagation(); + if (e.target === optActive) { + break; + } + e.target.click(); + break; + } + } + + if (typeof fileInput.webkitdirectory === strUndef) { + addClass(uploadType, classNone); + return; + } + optDirFile && removeClass(optDirFile, classHidden); + optInnerDirFile && removeClass(optInnerDirFile, classHidden); + + if (optFile) { + optFile.addEventListener('click', onClickOptFile); + optFile.addEventListener('keydown', onKeydownOpt); + } + if (optDirFile) { + optDirFile.addEventListener('click', onClickOptDirFile); + optDirFile.addEventListener('keydown', onKeydownOpt); + } + if (optInnerDirFile) { + optInnerDirFile.addEventListener('click', onClickOptInnerDirFile); + optInnerDirFile.addEventListener('keydown', onKeydownOpt); + } + + if (hasStorage) { + var uploadTypeField = 'upload-type'; + var prevUploadType = sessionStorage.getItem(uploadTypeField); + sessionStorage.removeItem(uploadTypeField); + + window.addEventListener(leavingEvent, function () { + var activeUploadType = fileInput.name; + if (activeUploadType !== file) { + sessionStorage.setItem(uploadTypeField, activeUploadType) + } + }, false); + + if (prevUploadType === dirFile) { + optDirFile && optDirFile.click(); + } else if (prevUploadType === innerDirFile) { + optInnerDirFile && optInnerDirFile.click(); + } + } + + optFile && fileInput.addEventListener('change', function (e) { + // workaround fix for mobile device, select dir not work but still act like select files + // switch back to file + if (optActive === optFile) { + return; + } + var files = e.target.files; + if (!files || !files.length) { + return; + } + + var nodir = Array.prototype.slice.call(files).every(function (file) { + return !file.webkitRelativePath || file.webkitRelativePath.indexOf('/') < 0; + }); + if (nodir) { + onClickOptFile(); // prevent clear input files + } + }); + + switchToFileMode = function () { + if (optFile && optActive !== optFile) { + optFile.focus(); + onClickOptFile(true); + } + } + + switchToDirMode = function () { + if (optDirFile) { + if (optActive !== optDirFile) { + optDirFile.focus(); + onClickOptDirFile(); + } + } else if (optInnerDirFile) { + if (optActive !== optInnerDirFile) { + optInnerDirFile.focus(); + onClickOptInnerDirFile(); + } + } + } + } + + function enableUploadProgress() { // also fix Safari upload filename has no path info + if (typeof FormData === strUndef) { + return; + } + + var uploading = false; + var batches = []; + var classUploading = 'uploading'; + var classFailed = 'failed'; + var elUploadStatus = document.body.querySelector('.upload-status'); + var elProgress = elUploadStatus && elUploadStatus.querySelector('.progress'); + var elFailedMessage = elUploadStatus && elUploadStatus.querySelector('.warn .message'); + + function onComplete() { + if (elProgress) { + elProgress.style.width = ''; + } + } + + function onSuccess() { + if (batches.length) { + return uploadBatch(batches.shift()); // use "return" for tail call optimize + } else { + uploading = false; + removeClass(elUploadStatus, classUploading); + } + } + + function onFail(e) { + removeClass(elUploadStatus, classUploading); + addClass(elUploadStatus, classFailed); + if (elFailedMessage) { + elFailedMessage.textContent = " - " + e.type; + } + batches.length = 0; + } + + function onLoad() { + var status = this.status; + if (status >= 200 && status <= 299) { + !uploading && location.reload(); + } else { + onFail({type: this.statusText || status}); + } + } + + function onProgress(e) { + if (e.lengthComputable) { + var percent = 100 * e.loaded / e.total; + elProgress.style.width = percent + '%'; + } + } + + function uploadProgressively(files) { + if (!files || !files.length) { + return; + } + + if (uploading) { + batches.push(files); + } else { + uploading = true; + removeClass(elUploadStatus, classFailed); + addClass(elUploadStatus, classUploading); + uploadBatch(files); + } + } + + function uploadBatch(files) { + var formName = fileInput.name; + var parts = new FormData(); + files.forEach(function (file) { + var relativePath + if (file.file) { + // unwrap object {file, relativePath} + relativePath = file.relativePath; + file = file.file; + } else if (file.webkitRelativePath) { + relativePath = file.webkitRelativePath + } + if (!relativePath) { + relativePath = file.name; + } + + parts.append(formName, file, relativePath); + }); + + var xhr = new XMLHttpRequest(); + xhr.addEventListener('error', onComplete); + xhr.addEventListener('error', onFail); + xhr.addEventListener('abort', onComplete); + xhr.addEventListener('abort', onFail); + xhr.addEventListener('load', onComplete); + xhr.addEventListener('load', onSuccess); + xhr.addEventListener('load', onLoad); + if (elProgress) { + xhr.upload.addEventListener('progress', onProgress); + } + + xhr.open(form.method, form.action); + xhr.send(parts); + } + + return uploadProgressively; + } + + function enableFormUploadProgress(uploadProgressively) { + form.addEventListener('submit', function (e) { + e.stopPropagation(); + e.preventDefault(); + + var files = Array.prototype.slice.call(fileInput.files); + uploadProgressively(files); + }); + + fileInput.addEventListener('change', function () { + var files = Array.prototype.slice.call(fileInput.files); + uploadProgressively(files); + }); + } + + function enableAddDragDrop(uploadProgressively) { + var isSelfDragging = false; + var classDragging = 'dragging'; + + function onSelfDragStart() { + isSelfDragging = true; + } + + function onDragEnd() { + isSelfDragging = false; + } + + function onDragEnterOver(e) { + if (!isSelfDragging) { + e.stopPropagation(); + e.preventDefault(); + addClass(e.currentTarget, classDragging); + } + } + + function onDragLeave(e) { + if (e.target === e.currentTarget) { + removeClass(e.currentTarget, classDragging); + } + } + + function onDrop(e) { + e.stopPropagation(); + e.preventDefault(); + removeClass(e.currentTarget, classDragging); + fileInput.value = ''; + + if (!e.dataTransfer || !e.dataTransfer.files || !e.dataTransfer.files.length) { + return; + } + + dataTransferToFiles(e.dataTransfer, canMkdir && Boolean(uploadProgressively), function (files, hasDir) { + if (hasDir) { + switchToDirMode(); + uploadProgressively(files); + } else { + switchToFileMode(); + if (uploadProgressively) { + uploadProgressively(files); + } else { + fileInput.files = files; + form.submit(); + } + } + }, function () { + typeof showUploadDirFailMessage !== strUndef && showUploadDirFailMessage(); + }); + } + + document.body.addEventListener('dragstart', onSelfDragStart); + document.body.addEventListener('dragend', onDragEnd); + var dragDropEl = document.documentElement; + dragDropEl.addEventListener('dragenter', onDragEnterOver); + dragDropEl.addEventListener('dragover', onDragEnterOver); + dragDropEl.addEventListener('dragleave', onDragLeave); + dragDropEl.addEventListener('drop', onDrop); + } + + function enableAddPasteProgressively(uploadProgressively) { + var typeTextPlain = 'text/plain'; + var createTextFile; + var textFilename = 'text.txt'; + if (Blob && Blob.prototype.msClose) { // legacy Edge + createTextFile = function (content) { + var file = new Blob([content], {type: typeTextPlain}); + file.name = textFilename; + return file; + }; + } else if (File) { + createTextFile = function (content) { + return new File([content], textFilename, {type: typeTextPlain}); + } + } + + var nonTextInputTypes = ['hidden', 'radio', 'checkbox', 'button', 'reset', 'submit', 'image']; + + function uploadPastedFiles(files) { + switchToFileMode(); + var ts = getTimeStamp(); + files = files.map(function (f, i) { + var filename = f.name; + var dotIndex = filename.lastIndexOf('.'); + if (dotIndex < 0) { + dotIndex = filename.length; + } + filename = filename.substring(0, dotIndex) + ts + '-' + i + filename.substring(dotIndex); + return { + file: f, + relativePath: filename + } + }); + uploadProgressively(files); + } + + function generatePastedFiles(data) { + var files; + var items; + if (data.files && data.files.length) { + // pasted content is image + files = Array.prototype.slice.call(data.files); + } else if (data.items && data.items.length) { + // pasted content is text + items = Array.prototype.slice.call(data.items); + files = items.map(function (item) { + return item.getAsFile(); + }).filter(Boolean); + } else { + files = []; + } + + if (files.length) { + uploadPastedFiles(files); + return; + } + + if (!createTextFile) { + return; + } + if (!items) { + return; + } + var plainTextFiles = 0; + for (var i = 0, itemsCount = items.length; i < itemsCount; i++) { + if (data.types[i] !== typeTextPlain) { + continue + } + plainTextFiles++; + items[i].getAsString(function (content) { + var file = createTextFile(content); + files.push(file); + if (files.length === plainTextFiles) { + uploadPastedFiles(files); + } + }); + } + } + + document.documentElement.addEventListener('paste', function (e) { + var tagName = e.target.tagName; + if (tagName === 'INPUT') { + if (nonTextInputTypes.indexOf(e.target.type) < 0) { + return; + } + } + if (tagName === 'TEXTAREA') { + return; + } + var data = e.clipboardData; + if (!data) { + return; + } + + var items = data.items; + if (!items || !items.length) { + generatePastedFiles(data); + return; + } + + itemsToFiles(items, canMkdir, function (files, hasDir) { + // for pasted text + if (!files.length) { + generatePastedFiles(data); + return; + } + + // suppose for pasted image data + if (files.length === 1 && files[0].file.type === 'image/png') { + files = files.map(function (fileInfo) { + return fileInfo && fileInfo.file; + }); + generatePastedFiles({files: files}); + return; + } + + // pasted real files + if (hasDir) { + switchToDirMode(); + } else { + switchToFileMode(); + } + uploadProgressively(files); + }, function () { + typeof showUploadDirFailMessage !== strUndef && showUploadDirFailMessage(); + }); + }); + } + + function enableAddPasteFormSubmit() { + document.documentElement.addEventListener('paste', function (e) { + var data = e.clipboardData; + if (data && data.files && data.files.length) { + switchToFileMode(); + fileInput.files = data.files; + form.submit(); + } + }); + } + + enableAddDirFile(); + var uploadProgressively = enableUploadProgress(); + if (uploadProgressively) { + enableFormUploadProgress(uploadProgressively); + enableAddPasteProgressively(uploadProgressively); + } else { + enableAddPasteFormSubmit(); + } + enableAddDragDrop(uploadProgressively); + } + + function enableNonRefreshDelete() { + if (!document.querySelector) { + return; + } + + var itemList = document.body.querySelector('.item-list'); + if (!itemList || !itemList.addEventListener) { + return; + } + if (!hasClass(itemList, 'has-deletable')) return; + + itemList.addEventListener('submit', function (e) { + if (e.defaultPrevented) { + return; + } + + var form = e.target; + + function onLoad() { + var status = this.status; + if (status >= 200 && status <= 299) { + var elItem = form; + while (elItem && elItem.nodeName !== 'LI') { + elItem = elItem.parentElement; + } + if (!elItem) { + return; + } + var elItemParent = elItem.parentNode; + elItemParent && elItemParent.removeChild(elItem); + } else { + logError('delete failed: ' + status + ' ' + this.statusText); + } + } + + var params = ''; + var els = Array.prototype.slice.call(form.elements); + for (var i = 0, len = els.length; i < len; i++) { + if (!els[i].name) { + continue + } + if (params.length > 0) { + params += '&' + } + params += els[i].name + '=' + encodeURIComponent(els[i].value) + } + var url = form.action; + + var xhr = new XMLHttpRequest(); + xhr.open('POST', url); // will retrieve deleted result into bfcache + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.addEventListener('load', onLoad); + xhr.send(params); + e.preventDefault(); + return false; + }, false); + } + + enableFilter(); + keepFocusOnBackwardForward(); + focusChildOnNavUp(); + enableKeyboardNavigate(); + enhanceUpload(); + enableNonRefreshDelete(); +})(); +` diff --git a/src/tpl/theme/dirTheme.go b/src/tpl/theme/dirTheme.go index e0cc3564..0451dc55 100644 --- a/src/tpl/theme/dirTheme.go +++ b/src/tpl/theme/dirTheme.go @@ -2,8 +2,8 @@ package theme import ( "io" + "io/ioutil" "net/http" - "os" "path" "strings" ) @@ -12,7 +12,7 @@ type DirTheme string func (dir DirTheme) RenderPage(w io.Writer, data interface{}) error { filename := string(dir) + "/" + templateFilename - tplStr, err := os.ReadFile(filename) + tplStr, err := ioutil.ReadFile(filename) if err != nil { return err } diff --git a/src/tpl/theme/memTheme.go b/src/tpl/theme/memTheme.go index e599f19c..8fed7b3c 100644 --- a/src/tpl/theme/memTheme.go +++ b/src/tpl/theme/memTheme.go @@ -5,6 +5,7 @@ import ( "errors" "html/template" "io" + "io/ioutil" "net/http" "time" ) @@ -37,7 +38,7 @@ func LoadMemTheme(themePath string) (theme MemTheme, err error) { continue } var raw []byte - raw, err = io.ReadAll(rd) + raw, err = ioutil.ReadAll(rd) rd.Close() if err != nil { return diff --git a/src/util/formatTime.go b/src/util/formatTime.go index 25f69723..1da790cc 100644 --- a/src/util/formatTime.go +++ b/src/util/formatTime.go @@ -1,6 +1,9 @@ package util -import "time" +import ( + "mjpclab.dev/ghfs/src/shimgo" + "time" +) const timeLayout = "2006-01-02 15:04:05" @@ -9,5 +12,5 @@ func FormatTimeSecond(t time.Time) string { } func AppendTimeSecond(buf []byte, t time.Time) []byte { - return t.AppendFormat(buf, timeLayout) + return shimgo.Time_AppendFormat(t, buf, timeLayout) } diff --git a/src/util/hostname.go b/src/util/hostname.go index 5d73ae2a..47ad5db3 100644 --- a/src/util/hostname.go +++ b/src/util/hostname.go @@ -1,6 +1,9 @@ package util -import "strings" +import ( + "mjpclab.dev/ghfs/src/shimgo" + "strings" +) func ExtractHostnamePort(host string) (hostname, port string) { if len(host) == 0 { @@ -11,7 +14,7 @@ func ExtractHostnamePort(host string) (hostname, port string) { if hostname[len(hostname)-1] != ']' { // not [IPv6] if colonIndex := strings.LastIndex(hostname, "]:"); colonIndex > 0 { // [IPv6]:port hostname = hostname[:colonIndex+1] - } else if colonIndex = strings.LastIndexByte(hostname, ':'); colonIndex >= 0 { + } else if colonIndex = shimgo.Strings_LastIndexByte(hostname, ':'); colonIndex >= 0 { hostname = hostname[:colonIndex] } } @@ -30,7 +33,7 @@ func ExtractListenPort(listen string) string { return listen[colonIndex+2:] } else if listen[len(listen)-1] == ']' { // [IPv6] return "" - } else if colonIndex = strings.LastIndexByte(listen, ':'); colonIndex >= 0 { + } else if colonIndex = shimgo.Strings_LastIndexByte(listen, ':'); colonIndex >= 0 { return listen[colonIndex+1:] } else { lenListen := len(listen) diff --git a/src/util/str.go b/src/util/str.go index 719eaa25..0d26c68a 100644 --- a/src/util/str.go +++ b/src/util/str.go @@ -73,7 +73,8 @@ func EscapeControllingRune(str string) []byte { buf = append(buf, '\\', 'x', h, l) } } else { - buf = utf8.AppendRune(buf, r) + nBytes := utf8.EncodeRune(runeBytes, r) + buf = append(buf, runeBytes[:nBytes]...) } }