Initial commit

This commit is contained in:
NI
2019-08-07 15:56:51 +08:00
commit 02f14eb14f
206 changed files with 38863 additions and 0 deletions

View File

@@ -0,0 +1,132 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"net/http"
"strings"
"github.com/niruix/sshwifty/application/log"
)
// Error
var (
ErrControllerNotImplemented = NewError(
http.StatusNotImplemented, "Server does not know how to handle the "+
"request")
)
type controller interface {
Get(w http.ResponseWriter, r *http.Request, l log.Logger) error
Head(w http.ResponseWriter, r *http.Request, l log.Logger) error
Post(w http.ResponseWriter, r *http.Request, l log.Logger) error
Put(w http.ResponseWriter, r *http.Request, l log.Logger) error
Delete(w http.ResponseWriter, r *http.Request, l log.Logger) error
Connect(w http.ResponseWriter, r *http.Request, l log.Logger) error
Options(w http.ResponseWriter, r *http.Request, l log.Logger) error
Trace(w http.ResponseWriter, r *http.Request, l log.Logger) error
Patch(w http.ResponseWriter, r *http.Request, l log.Logger) error
Other(
method string,
w http.ResponseWriter,
r *http.Request,
l log.Logger,
) error
}
type baseController struct{}
func (b baseController) Get(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Head(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Post(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Put(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Delete(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Connect(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Options(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Trace(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Patch(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func (b baseController) Other(
method string, w http.ResponseWriter, r *http.Request, l log.Logger) error {
return ErrControllerNotImplemented
}
func serveController(
c controller,
w http.ResponseWriter,
r *http.Request,
l log.Logger,
) error {
switch strings.ToUpper(r.Method) {
case "GET":
return c.Get(w, r, l)
case "HEAD":
return c.Head(w, r, l)
case "POST":
return c.Post(w, r, l)
case "PUT":
return c.Put(w, r, l)
case "DELETE":
return c.Delete(w, r, l)
case "CONNECT":
return c.Connect(w, r, l)
case "OPTIONS":
return c.Options(w, r, l)
case "TRACE":
return c.Trace(w, r, l)
case "PATCH":
return c.Patch(w, r, l)
default:
return c.Other(r.Method, w, r, l)
}
}

View File

@@ -0,0 +1,75 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"net/http"
"strings"
"time"
)
func clientSupportGZIP(r *http.Request) bool {
// Should be good enough
return strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
}
func clientContentEtagIsValid(r *http.Request, eTag string) bool {
d := r.Header.Get("If-None-Match")
if len(d) < 0 {
return false
}
dStart := 0
qETag := "\"" + eTag + "\""
for {
dIdx := strings.Index(d[dStart:], ",")
if dIdx < 0 {
return strings.Contains(d[dStart:], qETag) ||
strings.Contains(d[dStart:], "*")
}
if strings.Contains(d[dStart:dStart+dIdx], qETag) {
return true
}
if strings.Contains(d[dStart:dStart+dIdx], "*") {
return true
}
dStart += dIdx + 1
}
}
func clientContentModifiedSince(r *http.Request, mod time.Time) bool {
d := r.Header.Get("If-Modified-Since")
if len(d) < 0 {
return false
}
dt, dtErr := time.Parse(time.RFC1123, d)
if dtErr != nil {
return false
}
return !mod.Before(dt)
}

View File

@@ -0,0 +1,47 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"net/http"
"testing"
)
func TestClientContentEtagIsValid(t *testing.T) {
test := func(id int, hd []string, etag string, expected bool) {
r := http.Request{
Header: http.Header{
"If-None-Match": hd,
},
}
rr := clientContentEtagIsValid(&r, etag)
if rr != expected {
t.Errorf("Test: %d: Expecting the result to be %v, got %v instead",
id, expected, rr)
return
}
}
test(0, []string{""}, "test", false)
test(1, []string{"*"}, "test", true)
test(2, []string{"W/\"67ab43\", \"54ed21\", \"7892dd\""}, "54ed21", true)
test(3, []string{"\"bfc13a64729c4290ef5b2c2730249c88ca92d82d\""},
"bfc13a64729c4290ef5b2c2730249c88ca92d82d", true)
}

View File

@@ -0,0 +1,134 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"net/http"
"strings"
"time"
"github.com/niruix/sshwifty/application/command"
"github.com/niruix/sshwifty/application/configuration"
"github.com/niruix/sshwifty/application/log"
"github.com/niruix/sshwifty/application/server"
)
// Errors
var (
ErrNotFound = NewError(
http.StatusNotFound, "Page not found")
)
// handler is the main service dispatcher
type handler struct {
hostNameChecker string
commonCfg configuration.Common
logger log.Logger
homeCtl home
socketCtl socket
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var err error
clientLogger := h.logger.Context("Client (%s)", r.RemoteAddr)
if len(h.commonCfg.HostName) > 0 {
hostPort := r.Host
if len(hostPort) <= 0 {
hostPort = r.URL.Host
}
if h.commonCfg.HostName != hostPort &&
!strings.HasPrefix(hostPort, h.hostNameChecker) {
clientLogger.Warning("Request invalid host \"%s\", deined",
r.Host)
serveFailure(
NewError(http.StatusForbidden, "Invalid host"), w, r, h.logger)
return
}
}
w.Header().Add("Date", time.Now().UTC().Format(time.RFC1123))
switch r.URL.Path {
case "/":
err = serveController(h.homeCtl, w, r, clientLogger)
case "/socket":
err = serveController(h.socketCtl, w, r, clientLogger)
case "/robots.txt":
fallthrough
case "/favicon.ico":
fallthrough
case "/README.md":
fallthrough
case "/LICENSE.md":
fallthrough
case "/DEPENDENCES.md":
err = serveStaticData(r.URL.Path[1:], w, r, clientLogger)
default:
if strings.HasPrefix(r.URL.Path, "/assets/") {
err = serveStaticData(r.URL.Path[8:], w, r, clientLogger)
} else {
err = ErrNotFound
}
}
if err == nil {
clientLogger.Info("Request completed: %s", r.URL.String())
return
}
clientLogger.Warning("Request ended with error: %s: %s",
r.URL.String(), err)
controllerErr, isControllerErr := err.(Error)
if isControllerErr {
serveFailure(controllerErr, w, r, h.logger)
return
}
serveFailure(
NewError(http.StatusInternalServerError, err.Error()), w, r, h.logger)
}
// Builder returns a http controller builder
func Builder(cmds command.Commands) server.HandlerBuilder {
return func(
commonCfg configuration.Common,
cfg configuration.Server,
logger log.Logger,
) http.Handler {
return handler{
hostNameChecker: commonCfg.HostName + ":",
commonCfg: commonCfg,
logger: logger,
homeCtl: home{},
socketCtl: newSocketCtl(commonCfg, cfg, cmds),
}
}
}

View File

@@ -0,0 +1,44 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import "fmt"
// Error Controller error
type Error struct {
code int
message string
}
// NewError creates a new Error
func NewError(code int, message string) Error {
return Error{
code: code,
message: message,
}
}
// Code return the error code
func (f Error) Code() int {
return f.code
}
// Error returns the error message
func (f Error) Error() string {
return fmt.Sprintf("HTTP Error (%d): %s", f.code, f.message)
}

View File

@@ -0,0 +1,35 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"net/http"
"github.com/niruix/sshwifty/application/log"
)
func serveFailure(
err Error,
w http.ResponseWriter,
r *http.Request,
l log.Logger,
) error {
w.WriteHeader(err.Code())
return serveStaticPage("error.html", w, r, l)
}

View File

@@ -0,0 +1,33 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"net/http"
"github.com/niruix/sshwifty/application/log"
)
// home controller
type home struct {
baseController
}
func (h home) Get(w http.ResponseWriter, r *http.Request, l log.Logger) error {
return serveStaticPage("index.html", w, r, l)
}

View File

@@ -0,0 +1,444 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"encoding/base64"
"fmt"
"io"
"net/http"
"strconv"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/niruix/sshwifty/application/command"
"github.com/niruix/sshwifty/application/configuration"
"github.com/niruix/sshwifty/application/log"
"github.com/niruix/sshwifty/application/rw"
)
// Errors
var (
ErrSocketAuthFailed = NewError(
http.StatusForbidden,
"To use Websocket interface, a valid Auth Key must be provided")
ErrSocketUnableToGenerateKey = NewError(
http.StatusInternalServerError,
"Unable to generate crypto key")
ErrSocketInvalidDataPackage = NewError(
http.StatusBadRequest, "Invalid data package")
)
const (
socketGCMStandardNonceSize = 12
)
type socket struct {
baseController
commonCfg configuration.Common
serverCfg configuration.Server
randomKey string
authKey []byte
upgrader websocket.Upgrader
commander command.Commander
}
func getNewSocketCtlRandomSharedKey() string {
b := [32]byte{}
io.ReadFull(rand.Reader, b[:])
return base64.StdEncoding.EncodeToString(b[:])
}
func getSocketAuthKey(randomKey string, sharedKey string) []byte {
var k []byte
if len(sharedKey) > 0 {
k = []byte(sharedKey)
} else {
k = []byte(randomKey)
}
h := hmac.New(sha512.New, k)
h.Write([]byte(randomKey))
return h.Sum(nil)
}
func newSocketCtl(
commonCfg configuration.Common,
cfg configuration.Server,
cmds command.Commands,
) socket {
randomKey := getNewSocketCtlRandomSharedKey()
return socket{
commonCfg: commonCfg,
serverCfg: cfg,
randomKey: randomKey,
authKey: getSocketAuthKey(randomKey, commonCfg.SharedKey)[:32],
upgrader: buildWebsocketUpgrader(cfg),
commander: command.New(cmds),
}
}
type websocketWriter struct {
*websocket.Conn
}
func (w websocketWriter) Write(b []byte) (int, error) {
wErr := w.WriteMessage(websocket.BinaryMessage, b)
if wErr != nil {
return 0, wErr
}
return len(b), nil
}
type socketPackageWriter struct {
w websocketWriter
packager func(w websocketWriter, b []byte) error
}
func (s socketPackageWriter) Write(b []byte) (int, error) {
packageWriteErr := s.packager(s.w, b)
if packageWriteErr != nil {
return 0, packageWriteErr
}
return len(b), nil
}
func buildWebsocketUpgrader(cfg configuration.Server) websocket.Upgrader {
return websocket.Upgrader{
HandshakeTimeout: cfg.InitialTimeout,
CheckOrigin: func(r *http.Request) bool {
return true
},
Error: func(
w http.ResponseWriter,
r *http.Request,
status int,
reason error,
) {
},
}
}
func (s socket) Options(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Headers", "X-Key")
return nil
}
func (s socket) setServerConfigHeader(hd *http.Header) {
hd.Add("X-Heartbeat",
strconv.FormatFloat(s.serverCfg.HeartbeatTimeout.Seconds(), 'g', 2, 64))
hd.Add("X-Timeout",
strconv.FormatFloat(s.serverCfg.ReadTimeout.Seconds(), 'g', 2, 64))
}
func (s socket) Head(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
key := r.Header.Get("X-Key")
hd := w.Header()
if len(key) <= 0 {
hd.Add("X-Key", s.randomKey)
if len(s.commonCfg.SharedKey) <= 0 {
s.setServerConfigHeader(&hd)
return nil
}
return ErrSocketAuthFailed
}
if len(key) > 64 {
return ErrSocketAuthFailed
}
decodedKey, decodedKeyErr := base64.StdEncoding.DecodeString(key)
if decodedKeyErr != nil {
return NewError(http.StatusBadRequest, decodedKeyErr.Error())
}
if !hmac.Equal(s.authKey, decodedKey) {
return ErrSocketAuthFailed
}
hd.Add("X-Key", s.randomKey)
s.setServerConfigHeader(&hd)
return nil
}
func (s socket) buildWSFetcher(c *websocket.Conn) rw.FetchReaderFetcher {
return func() ([]byte, error) {
for {
mt, message, err := c.ReadMessage()
if err != nil {
return nil, err
}
if mt != websocket.BinaryMessage {
return nil, NewError(
http.StatusBadRequest,
fmt.Sprintf("Received unknown type of data: %d", message))
}
return message, nil
}
}
}
func (s socket) generateNonce(nonce []byte) error {
_, rErr := io.ReadFull(rand.Reader, nonce[:socketGCMStandardNonceSize])
return rErr
}
func (s socket) increaseNonce(nonce []byte) {
for i := len(nonce); i > 0; i-- {
nonce[i-1]++
if nonce[i-1] <= 0 {
continue
}
break
}
}
func (s socket) createCipher(key []byte) (cipher.AEAD, cipher.AEAD, error) {
readCipher, readCipherErr := aes.NewCipher(key)
if readCipherErr != nil {
return nil, nil, readCipherErr
}
writeCipher, writeCipherErr := aes.NewCipher(key)
if writeCipherErr != nil {
return nil, nil, writeCipherErr
}
gcmRead, gcmReadErr := cipher.NewGCMWithNonceSize(
readCipher, socketGCMStandardNonceSize)
if gcmReadErr != nil {
return nil, nil, gcmReadErr
}
gcmWrite, gcmWriteErr := cipher.NewGCMWithNonceSize(
writeCipher, socketGCMStandardNonceSize)
if gcmWriteErr != nil {
return nil, nil, gcmWriteErr
}
return gcmRead, gcmWrite, nil
}
func (s socket) privateKey() string {
if len(s.commonCfg.SharedKey) > 0 {
return s.commonCfg.SharedKey
}
return s.randomKey
}
func (s socket) buildCipherKey() [24]byte {
key := [24]byte{}
now := strconv.FormatInt(time.Now().Unix()/100, 10)
copy(key[:], getSocketAuthKey(now, s.privateKey()))
return key
}
func (s socket) Get(
w http.ResponseWriter, r *http.Request, l log.Logger) error {
// Error will not be returned when Websocket already handled
// (i.e. returned the error to client). We just log the error and that's it
c, err := s.upgrader.Upgrade(w, r, nil)
if err != nil {
return NewError(http.StatusBadRequest, err.Error())
}
defer c.Close()
wsReader := rw.NewFetchReader(s.buildWSFetcher(c))
wsWriter := websocketWriter{Conn: c}
// Initialize ciphers
readNonce := [socketGCMStandardNonceSize]byte{}
_, nonceReadErr := io.ReadFull(&wsReader, readNonce[:])
if nonceReadErr != nil {
return NewError(http.StatusBadRequest, fmt.Sprintf(
"Unable to read initial client nonce: %s", nonceReadErr.Error()))
}
writeNonce := [socketGCMStandardNonceSize]byte{}
nonceReadErr = s.generateNonce(writeNonce[:])
if nonceReadErr != nil {
return NewError(http.StatusBadRequest, fmt.Sprintf(
"Unable to generate initial server nonce: %s",
nonceReadErr.Error()))
}
_, nonceSendErr := wsWriter.Write(writeNonce[:])
if nonceSendErr != nil {
return NewError(http.StatusBadRequest, fmt.Sprintf(
"Unable to send server nonce to client: %s", nonceSendErr.Error()))
}
cipherKey := s.buildCipherKey()
readCipher, writeCipher, cipherCreationErr := s.createCipher(cipherKey[:])
if cipherCreationErr != nil {
return NewError(http.StatusInternalServerError, fmt.Sprintf(
"Unable to create cipher: %s", cipherCreationErr.Error()))
}
// Start service
const cipherReadBufSize = 1024
cipherReadBuf := [cipherReadBufSize]byte{}
cipherWriteBuf := [cipherReadBufSize]byte{}
maxWriteLen := cipherReadBufSize - (writeCipher.Overhead() + 2)
senderLock := sync.Mutex{}
cmdExec, cmdExecErr := s.commander.New(
s.commonCfg.Dialer, rw.NewFetchReader(func() ([]byte, error) {
defer s.increaseNonce(readNonce[:])
// Size is unencrypted
_, rErr := io.ReadFull(&wsReader, cipherReadBuf[:2])
if rErr != nil {
return nil, rErr
}
// Read full size
packageSize := uint16(cipherReadBuf[0])
packageSize <<= 8
packageSize |= uint16(cipherReadBuf[1])
if packageSize <= 0 {
return nil, ErrSocketInvalidDataPackage
}
if int(packageSize) <= wsReader.Remain() {
rData, rErr := wsReader.Export(int(packageSize))
if rErr != nil {
return nil, rErr
}
return readCipher.Open(rData[:0], readNonce[:], rData, nil)
}
if packageSize > cipherReadBufSize {
return nil, ErrSocketInvalidDataPackage
}
_, rErr = io.ReadFull(&wsReader, cipherReadBuf[:packageSize])
if rErr != nil {
return nil, rErr
}
return readCipher.Open(
cipherReadBuf[:0],
readNonce[:],
cipherReadBuf[:packageSize],
nil)
}), socketPackageWriter{
w: wsWriter,
packager: func(w websocketWriter, b []byte) error {
start := 0
bLen := len(b)
readLen := bLen
for start < bLen {
if readLen > maxWriteLen {
readLen = maxWriteLen
}
encrypted := writeCipher.Seal(
cipherWriteBuf[2:2],
writeNonce[:],
b[start:start+readLen],
nil)
s.increaseNonce(writeNonce[:])
encryptedSize := uint16(len(encrypted))
if encryptedSize <= 0 {
return ErrSocketInvalidDataPackage
}
cipherWriteBuf[0] = byte(encryptedSize >> 8)
cipherWriteBuf[1] = byte(encryptedSize)
_, wErr := w.Write(cipherWriteBuf[:encryptedSize+2])
if wErr != nil {
return wErr
}
start += readLen
readLen = bLen - start
}
return nil
},
}, &senderLock, s.serverCfg.ReadDelay, s.serverCfg.WriteDelay, l)
if cmdExecErr != nil {
return NewError(http.StatusBadRequest, cmdExecErr.Error())
}
return cmdExec.Handle()
}

View File

@@ -0,0 +1,154 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
import (
"mime"
"net/http"
"strings"
"time"
"github.com/niruix/sshwifty/application/log"
)
type staticData struct {
data []byte
dataHash string
compressd []byte
compressdHash string
created time.Time
}
func (s staticData) hasCompressed() bool {
return len(s.compressd) > 0
}
func staticFileExt(fileName string) string {
extIdx := strings.LastIndex(fileName, ".")
if extIdx < 0 {
return ""
}
return strings.ToLower(fileName[extIdx:])
}
func serveStaticPage(
dataName string,
w http.ResponseWriter,
r *http.Request,
l log.Logger,
) error {
d, dFound := staticPages[dataName]
if !dFound {
return ErrNotFound
}
selectedData := d.data
if clientSupportGZIP(r) && d.hasCompressed() {
selectedData = d.compressd
w.Header().Add("Vary", "Accept-Encoding")
w.Header().Add("Content-Encoding", "gzip")
}
mimeType := mime.TypeByExtension(staticFileExt(dataName))
if len(mimeType) > 0 {
w.Header().Add("Content-Type", mimeType)
} else {
w.Header().Add("Content-Type", "application/binary")
}
_, wErr := w.Write(selectedData)
return wErr
}
func serveStaticData(
dataName string,
w http.ResponseWriter,
r *http.Request,
l log.Logger,
) error {
if strings.ToUpper(r.Method) != "GET" {
return ErrControllerNotImplemented
}
fileExt := staticFileExt(dataName)
if fileExt == ".html" || fileExt == ".htm" {
return ErrNotFound
}
d, dFound := staticPages[dataName]
if !dFound {
return ErrNotFound
}
selectedData := d.data
selectedDataHash := d.dataHash
compressEnabled := false
if clientSupportGZIP(r) && d.hasCompressed() {
selectedData = d.compressd
selectedDataHash = d.compressdHash
compressEnabled = true
w.Header().Add("Vary", "Accept-Encoding")
}
canUseCache := true
if !clientContentEtagIsValid(r, selectedDataHash) {
canUseCache = false
}
if clientContentModifiedSince(r, d.created) {
canUseCache = false
}
if canUseCache {
w.WriteHeader(http.StatusNotModified)
return nil
}
w.Header().Add("Cache-Control", "public, max-age=31536000")
w.Header().Add("ETag", "\""+selectedDataHash+"\"")
mimeType := mime.TypeByExtension(fileExt)
if len(mimeType) > 0 {
w.Header().Add("Content-Type", mimeType)
} else {
w.Header().Add("Content-Type", "application/binary")
}
if compressEnabled {
w.Header().Add("Content-Encoding", "gzip")
}
_, wErr := w.Write([]byte(selectedData))
return wErr
}

View File

@@ -0,0 +1,457 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"bufio"
"bytes"
"compress/gzip"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"text/template"
"time"
)
const (
parentPackage = "github.com/niruix/sshwifty/application/controller"
)
const (
staticListTemplate = `import "time"
var (
staticPages = map[string]staticData{
{{ range . }}"{{ .Name }}":
parseStaticData({{ .GOPackage }}.{{ .GOVariableName }}()),
{{ end }}
}
)
// parseStaticData parses result from a static file returner and generate
// a new ` + "`" + `staticData` + "`" + ` item
func parseStaticData(
fileStart int,
fileEnd int,
compressedStart int,
compressedEnd int,
contentHash string,
compressedHash string,
creation time.Time,
data []byte,
) staticData {
return staticData{
data: data[fileStart:fileEnd],
dataHash: contentHash,
compressd: data[compressedStart:compressedEnd],
compressdHash: compressedHash,
created: creation,
}
}
`
staticListTemplateDev = `import "io/ioutil"
import "bytes"
import "fmt"
import "compress/gzip"
import "encoding/base64"
import "time"
import "crypto/sha256"
// WARNING: THIS GENERATION IS FOR DEBUG / DEVELOPMENT ONLY, DO NOT
// USE IT IN PRODUCTION!
func staticFileGen(filePath string) staticData {
content, readErr := ioutil.ReadFile(filePath)
if readErr != nil {
panic(fmt.Sprintln("Cannot read file:", readErr))
}
compressed := bytes.NewBuffer(make([]byte, 0, 1024))
compresser, compresserBuildErr := gzip.NewWriterLevel(
compressed, gzip.BestCompression)
if compresserBuildErr != nil {
panic(fmt.Sprintln("Cannot build data compresser:", compresserBuildErr))
}
contentLen := len(content)
_, compressErr := compresser.Write(content)
if compressErr != nil {
panic(fmt.Sprintln("Cannot write compressed data:", compressErr))
}
compressErr = compresser.Flush()
if compressErr != nil {
panic(fmt.Sprintln("Cannot write compressed data:", compressErr))
}
content = append(content, compressed.Bytes()...)
getHash := func(b []byte) []byte {
h := sha256.New()
h.Write(b)
return h.Sum(nil)
}
return staticData{
data: content[0:contentLen],
dataHash: base64.StdEncoding.EncodeToString(
getHash(content[0:contentLen])[:8]),
compressd: content[contentLen:],
compressdHash: base64.StdEncoding.EncodeToString(
getHash(content[contentLen:])[:8]),
created: time.Now(),
}
}
var (
staticPages = map[string]staticData{
{{ range . }}"{{ .Name }}": staticFileGen(
"{{ .Path }}",
),
{{ end }}
}
)`
staticPageTemplate = `package {{ .GOPackage }}
// This file is part of Sshwifty Project
//
// Copyright (C) {{ .Date.Year }} Rui NI (nirui@gmx.com)
//
// https://github.com/niruix/sshwifty
//
// This file is generated at {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 MST" }}
// by "go generate", DO NOT EDIT! Also, do not open this file, it maybe too large
// for your editor. You've been warned.
//
// This file may contain third-party binaries. See DEPENDENCES for detail.
import (
"time"
"encoding/hex"
)
var raw{{ .GOVariableName }}Data = ` + "`" + `{{ .Data }}` + "`" + `
// {{ .GOVariableName }} returns static file
func {{ .GOVariableName }}() (
int, // FileStart
int, // FileEnd
int, // CompressedStart
int, // CompressedEnd
string, // ContentHash
string, // CompressedHash
time.Time, // Time of creation
[]byte, // Data
) {
created, createErr := time.Parse(
time.RFC1123, "{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 MST" }}")
if createErr != nil {
panic(createErr)
}
data, dataErr := hex.DecodeString(raw{{ .GOVariableName }}Data)
raw{{ .GOVariableName }}Data = ""
if dataErr != nil {
panic(dataErr)
}
return {{ .FileStart }}, {{ .FileEnd }},
{{ .CompressedStart }}, {{ .CompressedEnd }},
"{{ .ContentHash }}", "{{ .CompressedHash }}", created, data
}
`
)
const (
templateStarts = "//go:generate"
)
type parsedFile struct {
Name string
GOVariableName string
GOFileName string
GOPackage string
Path string
Data string
FileStart int
FileEnd int
CompressedStart int
CompressedEnd int
ContentHash string
CompressedHash string
Date time.Time
}
func getHash(b []byte) []byte {
h := sha256.New()
h.Write(b)
return h.Sum(nil)
}
func buildListFile(w io.Writer, data interface{}) error {
tpl := template.Must(template.New(
"StaticPageList").Parse(staticListTemplate))
return tpl.Execute(w, data)
}
func buildListFileDev(w io.Writer, data interface{}) error {
tpl := template.Must(template.New(
"StaticPageList").Parse(staticListTemplateDev))
return tpl.Execute(w, data)
}
func buildDataFile(w io.Writer, data interface{}) error {
tpl := template.Must(template.New(
"StaticPageData").Parse(staticPageTemplate))
return tpl.Execute(w, data)
}
func byteToArrayStr(b []byte) string {
return hex.EncodeToString(b)
}
func parseFile(
id int, name string, filePath string, packageName string) parsedFile {
content, readErr := ioutil.ReadFile(filePath)
if readErr != nil {
panic(fmt.Sprintln("Cannot read file:", readErr))
}
compressed := bytes.NewBuffer(make([]byte, 0, 1024))
compresser, compresserBuildErr := gzip.NewWriterLevel(
compressed, gzip.BestCompression)
if compresserBuildErr != nil {
panic(fmt.Sprintln("Cannot build data compresser:", compresserBuildErr))
}
contentLen := len(content)
_, compressErr := compresser.Write(content)
if compressErr != nil {
panic(fmt.Sprintln("Cannot write compressed data:", compressErr))
}
compressErr = compresser.Flush()
if compressErr != nil {
panic(fmt.Sprintln("Cannot write compressed data:", compressErr))
}
content = append(content, compressed.Bytes()...)
goFileName := "Static" + strconv.FormatInt(int64(id), 10)
return parsedFile{
Name: name,
GOVariableName: strings.Title(goFileName),
GOFileName: strings.ToLower(goFileName) + "_generated.go",
GOPackage: packageName,
Path: filePath,
Data: byteToArrayStr(content),
FileStart: 0,
FileEnd: contentLen,
CompressedStart: contentLen,
CompressedEnd: len(content),
ContentHash: base64.StdEncoding.EncodeToString(
getHash(content[0:contentLen])[:8]),
CompressedHash: base64.StdEncoding.EncodeToString(
getHash(content[contentLen:len(content)])[:8]),
Date: time.Now(),
}
}
func main() {
if len(os.Args) < 3 {
panic("Usage: <Source Folder> <(Destination) List File>")
}
sourcePath, sourcePathErr := filepath.Abs(os.Args[1])
if sourcePathErr != nil {
panic(fmt.Sprintf("Invalid source folder path %s: %s",
os.Args[1], sourcePathErr))
}
listFilePath, listFilePathErr := filepath.Abs(os.Args[2])
if listFilePathErr != nil {
panic(fmt.Sprintf("Invalid destination list file path %s: %s",
os.Args[2], listFilePathErr))
}
listFileName := filepath.Base(listFilePath)
destFolderPackage := strings.TrimSuffix(
listFileName, filepath.Ext(listFileName))
destFolderPath := filepath.Join(
filepath.Dir(listFilePath), destFolderPackage)
destFolderPathErr := os.RemoveAll(destFolderPath)
if destFolderPathErr != nil {
panic(fmt.Sprintf("Unable to remove data destination folder %s: %s",
destFolderPath, destFolderPathErr))
}
destFolderPathErr = os.Mkdir(destFolderPath, 0777)
if destFolderPathErr != nil {
panic(fmt.Sprintf("Unable to build data destination folder %s: %s",
destFolderPath, destFolderPathErr))
}
listFile, listFileErr := os.OpenFile(listFilePath, os.O_RDWR, 0666)
if listFileErr != nil {
panic(fmt.Sprintf("Unable to open destination list file %s: %s",
listFilePath, listFileErr))
}
defer listFile.Close()
files, dirOpenErr := ioutil.ReadDir(sourcePath)
if dirOpenErr != nil {
panic(fmt.Sprintf("Unable to open dir: %s", dirOpenErr))
}
scanner := bufio.NewScanner(listFile)
destBytesByPass := int64(0)
foundLastLine := false
for scanner.Scan() {
text := scanner.Text()
if strings.Index(text, templateStarts) < 0 {
if foundLastLine {
break
}
destBytesByPass += int64(len(text) + 1)
continue
}
destBytesByPass += int64(len(text) + 1)
foundLastLine = true
}
listFile.Seek(destBytesByPass, 0)
listFile.Truncate(destBytesByPass)
listFile.WriteString("\n// This file is generated by `go generate` at " +
time.Now().Format(time.RFC1123) + "\n// DO NOT EDIT!\n\n")
switch os.Getenv("NODE_ENV") {
case "development":
type sourceFiles struct {
Name string
Path string
}
var sources []sourceFiles
for f := range files {
if !files[f].Mode().IsRegular() {
continue
}
sources = append(sources, sourceFiles{
Name: files[f].Name(),
Path: filepath.Join(sourcePath, files[f].Name()),
})
}
tempBuildErr := buildListFileDev(listFile, sources)
if tempBuildErr != nil {
panic(fmt.Sprintf(
"Unable to build destination file due to error: %s",
tempBuildErr))
}
default:
var parsedFiles []parsedFile
for f := range files {
if !files[f].Mode().IsRegular() {
continue
}
currentFilePath := filepath.Join(sourcePath, files[f].Name())
parsedFiles = append(parsedFiles, parseFile(
f, files[f].Name(), currentFilePath, destFolderPackage))
}
for f := range parsedFiles {
fn := filepath.Join(destFolderPath, parsedFiles[f].GOFileName)
ff, ffErr := os.Create(fn)
if ffErr != nil {
panic(fmt.Sprintf("Unable to create static page file %s: %s",
fn, ffErr))
}
bErr := buildDataFile(ff, parsedFiles[f])
if bErr != nil {
panic(fmt.Sprintf("Unable to build static page file %s: %s",
fn, bErr))
}
}
listFile.WriteString(
"\nimport \"" + parentPackage + "/" + destFolderPackage + "\"\n\n")
tempBuildErr := buildListFile(listFile, parsedFiles)
if tempBuildErr != nil {
panic(fmt.Sprintf(
"Unable to build destination file due to error: %s",
tempBuildErr))
}
}
}

View File

@@ -0,0 +1,128 @@
// Sshwifty - A Web SSH client
//
// Copyright (C) 2019 Rui NI <nirui@gmx.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package controller
//go:generate go run ./static_page_generater ../../.tmp/dist ./static_pages.go
//go:generate go fmt ./static_pages.go
// This file is generated by `go generate` at Wed, 07 Aug 2019 15:54:10 CST
// DO NOT EDIT!
import "github.com/niruix/sshwifty/application/controller/static_pages"
import "time"
var (
staticPages = map[string]staticData{
"13ec0eb5bdb821ff4930237d7c9f943f.woff2": parseStaticData(static_pages.Static0()),
"13efe6cbc10b97144a28310ebdeda594.woff": parseStaticData(static_pages.Static1()),
"1d6594826615607f6dc860bb49258acb.woff": parseStaticData(static_pages.Static2()),
"313a65630d341645c13e4f2a0364381d.woff": parseStaticData(static_pages.Static3()),
"35b07eb2f8711ae08d1f58c043880930.woff": parseStaticData(static_pages.Static4()),
"4357beb823a5f8d65c260f045d9e019a.woff2": parseStaticData(static_pages.Static5()),
"4fe0f73cc919ba2b7a3c36e4540d725c.woff": parseStaticData(static_pages.Static6()),
"50d75e48e0a3ddab1dd15d6bfb9d3700.woff": parseStaticData(static_pages.Static7()),
"59eb3601394dd87f30f82433fb39dd94.woff2": parseStaticData(static_pages.Static8()),
"5b4a33e176ff736a74f0ca2dd9e6b396.woff2": parseStaticData(static_pages.Static9()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-144x144.png": parseStaticData(static_pages.Static10()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-192x192.png": parseStaticData(static_pages.Static11()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-256x256.png": parseStaticData(static_pages.Static12()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-36x36.png": parseStaticData(static_pages.Static13()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-384x384.png": parseStaticData(static_pages.Static14()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-48x48.png": parseStaticData(static_pages.Static15()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-512x512.png": parseStaticData(static_pages.Static16()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-72x72.png": parseStaticData(static_pages.Static17()),
"6972f63e5242168cc5637e2771292ec0-android-chrome-96x96.png": parseStaticData(static_pages.Static18()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-114x114.png": parseStaticData(static_pages.Static19()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-120x120.png": parseStaticData(static_pages.Static20()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-144x144.png": parseStaticData(static_pages.Static21()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-152x152.png": parseStaticData(static_pages.Static22()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-167x167.png": parseStaticData(static_pages.Static23()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-180x180.png": parseStaticData(static_pages.Static24()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-57x57.png": parseStaticData(static_pages.Static25()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-60x60.png": parseStaticData(static_pages.Static26()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-72x72.png": parseStaticData(static_pages.Static27()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-76x76.png": parseStaticData(static_pages.Static28()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon-precomposed.png": parseStaticData(static_pages.Static29()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-icon.png": parseStaticData(static_pages.Static30()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-1182x2208.png": parseStaticData(static_pages.Static31()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-1242x2148.png": parseStaticData(static_pages.Static32()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-1496x2048.png": parseStaticData(static_pages.Static33()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-1536x2008.png": parseStaticData(static_pages.Static34()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-320x460.png": parseStaticData(static_pages.Static35()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-640x1096.png": parseStaticData(static_pages.Static36()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-640x920.png": parseStaticData(static_pages.Static37()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-748x1024.png": parseStaticData(static_pages.Static38()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-750x1294.png": parseStaticData(static_pages.Static39()),
"6972f63e5242168cc5637e2771292ec0-apple-touch-startup-image-768x1004.png": parseStaticData(static_pages.Static40()),
"6972f63e5242168cc5637e2771292ec0-favicon-16x16.png": parseStaticData(static_pages.Static41()),
"6972f63e5242168cc5637e2771292ec0-favicon-32x32.png": parseStaticData(static_pages.Static42()),
"6972f63e5242168cc5637e2771292ec0-favicon.ico": parseStaticData(static_pages.Static43()),
"6972f63e5242168cc5637e2771292ec0-firefox_app_128x128.png": parseStaticData(static_pages.Static44()),
"6972f63e5242168cc5637e2771292ec0-firefox_app_512x512.png": parseStaticData(static_pages.Static45()),
"6972f63e5242168cc5637e2771292ec0-firefox_app_60x60.png": parseStaticData(static_pages.Static46()),
"6972f63e5242168cc5637e2771292ec0-manifest.json": parseStaticData(static_pages.Static47()),
"6972f63e5242168cc5637e2771292ec0-manifest.webapp": parseStaticData(static_pages.Static48()),
"7237b0744491024097d3d.css": parseStaticData(static_pages.Static49()),
"7237b0744491024097d3d.css.map": parseStaticData(static_pages.Static50()),
"7237b0744491024097d3d.js": parseStaticData(static_pages.Static51()),
"73f0a88bbca1bec19fb1303c689d04c6.woff2": parseStaticData(static_pages.Static52()),
"83e114c316fcc3f23f524ec3e1c65984.woff": parseStaticData(static_pages.Static53()),
"8a96edbbcd9a6991d79371aed0b0288e.woff": parseStaticData(static_pages.Static54()),
"90d1676003d9c28c04994c18bfd8b558.woff2": parseStaticData(static_pages.Static55()),
"94008e69aaf05da75c0bbf8f8bb0db41.woff2": parseStaticData(static_pages.Static56()),
"DEPENDENCES.md": parseStaticData(static_pages.Static57()),
"LICENSE.md": parseStaticData(static_pages.Static58()),
"README.md": parseStaticData(static_pages.Static59()),
"ad538a69b0e8615ed0419c4529344ffc.woff2": parseStaticData(static_pages.Static60()),
"b52fac2bb93c5858f3f2675e4b52e1de.woff2": parseStaticData(static_pages.Static61()),
"c599374e350aa7225ee3e8a114e8d8d7.svg": parseStaticData(static_pages.Static62()),
"c73eb1ceba3321a80a0aff13ad373cb4.woff": parseStaticData(static_pages.Static63()),
"cc2fadc3928f2f223418887111947b40.woff": parseStaticData(static_pages.Static64()),
"d26871e8149b5759f814fd3c7a4f784b.woff2": parseStaticData(static_pages.Static65()),
"d3b47375afd904983d9be8d6e239a949.woff": parseStaticData(static_pages.Static66()),
"e8eaae902c3a4dacb9a5062667e10576.woff2": parseStaticData(static_pages.Static67()),
"ea4853ff509fe5f353d52586fcb94e20.svg": parseStaticData(static_pages.Static68()),
"f5902d5ef961717ed263902fc429e6ae.woff": parseStaticData(static_pages.Static69()),
"f75569f8a5fab0893fa712d8c0d9c3fe.woff2": parseStaticData(static_pages.Static70()),
"index.html": parseStaticData(static_pages.Static71()),
"manifest.json": parseStaticData(static_pages.Static72()),
"robots.txt": parseStaticData(static_pages.Static73()),
}
)
// parseStaticData parses result from a static file returner and generate
// a new `staticData` item
func parseStaticData(
fileStart int,
fileEnd int,
compressedStart int,
compressedEnd int,
contentHash string,
compressedHash string,
creation time.Time,
data []byte,
) staticData {
return staticData{
data: data[fileStart:fileEnd],
dataHash: contentHash,
compressd: data[compressedStart:compressedEnd],
compressdHash: compressedHash,
created: creation,
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
package static_pages
// This file is part of Sshwifty Project
//
// Copyright (C) 2019 Rui NI (nirui@gmx.com)
//
// https://github.com/niruix/sshwifty
//
// This file is generated at Wed, 07 Aug 2019 15:54:10 CST
// by "go generate", DO NOT EDIT! Also, do not open this file, it maybe too large
// for your editor. You've been warned.
//
// This file may contain third-party binaries. See DEPENDENCES for detail.
import (
"time"
"encoding/hex"
)
var rawStatic41Data = `89504e470d0a1a0a0000000d49484452000000100000001008060000001ff3ff6100000002494441547801ec1a7ed2000002f5494441542dc14d689c551480e1f7def9ee4c7e6a930c8d9a1447512aad92fa035584d68516110a5310eaa62bbb510b8ae822e84610c1c69592aec42e8a0b372ed2080aa250248aa042a5d22ada1012a9689be94ce3cc7ce7dc7bbe6b167d1ec72d6b2f2dceda787f1e68032d5cf6bea870c1f0a1c21756b960eb3ed8e7b59dfd53b79f387d956d8e6dabafbf7f30bbea1cd03432d7b3a22e8137f046f6891032adc9827a23e383ddf085b5779df870c5fdf9c6a959071781e6956ac84fb1cb3045524a98252c19658cf4ca9280d1ded7e4d0fd13b8609d5a11e70a07f340f30f1bb0a21d528c9825524a5832cc127d514484ada87cf45d0ff3333cbd7faa49c1bc07da89cc0f650751a5b2c49b47eee6f89333dc360a4311066589882022880867ceaf925cc4d5aded81d6bf55c94d2d115506226c5e13e6a677f0d6b1bd3c776837356f880a228aa870bd3be0f2df5d6ac15ade15952f9d22aa94220c4ae1dd2f2ff1f1d757e85d130e3f30c3e22b4ff0ece3bb1115440451a1371ce2ebe6bd0f46e512a28a444554198af0cde5bf78f5ecf77cf2d5eff821bc7c648e50cb880a224276091f8cc2070349882a3146628aa4188931d17030966a84be27fb4c590a228aaa927dc207a3f0c1c83ea1aa688cc418b194786afa4e8e3fb88789a9069bff0cf9e0b31fd9ec6d1135a251c92ee18251b8ba55a38dec45158d919c12efdd7580fb9a13c48171f6b78b9cf9e502ddfe008d4a8c11556572d2e3eb56153ed8fabdd38d7b8233fe538154b18b11beddd86071ed67d66e76508d685454231a951da370e0e19df8a25af7ae6ecb8d91ccb147ef4054d92a071cbdb0c4fca5f3ac763729451011440451415579e7b57d8c8d032e2ffb50ab167c489dc3fb9b9c7ca645a356514a8988202a8828a28288323e9239fdf643bcf07c0b1c1d7c5e706cbbf1e9c983ae96cfb95035638efcbad1a13b2cc92e814b646f4c4d7a1e7b6492b171c7b60e70d4ed5d5a71dcd25b7a71d61736ef83b55d51b57c30ef82e143852b0c1faacad56c1ddc32355d707bbeb8cab6ff01f23ad711d68e17be0000000049454e44ae4260821f8b08000000000002ff003c03c3fc89504e470d0a1a0a0000000d49484452000000100000001008060000001ff3ff6100000002494441547801ec1a7ed2000002f5494441542dc14d689c551480e1f7def9ee4c7e6a930c8d9a1447512aad92fa035584d68516110a5310eaa62bbb510b8ae822e84610c1c69592aec42e8a0b372ed2080aa250248aa042a5d22ada1012a9689be94ce3cc7ce7dc7bbe6b167d1ec72d6b2f2dceda787f1e68032d5cf6bea870c1f0a1c21756b960eb3ed8e7b59dfd53b79f387d956d8e6dabafbf7f30bbea1cd03432d7b3a22e8137f046f6891032adc9827a23e383ddf085b5779df870c5fdf9c6a959071781e6956ac84fb1cb3045524a98252c19658cf4ca9280d1ded7e4d0fd13b8609d5a11e70a07f340f30f1bb0a21d528c9825524a5832cc127d514484ada87cf45d0ff3333cbd7faa49c1bc07da89cc0f650751a5b2c49b47eee6f89333dc360a4311066589882022880867ceaf925cc4d5aded81d6bf55c94d2d115506226c5e13e6a677f0d6b1bd3c776837356f880a228aa870bd3be0f2df5d6ac15ade15952f9d22aa94220c4ae1dd2f2ff1f1d757e85d130e3f30c3e22b4ff0ece3bb1115440451a1371ce2ebe6bd0f46e512a28a444554198af0cde5bf78f5ecf77cf2d5eff821bc7c648e50cb880a224276091f8cc2070349882a3146628aa4188931d17030966a84be27fb4c590a228aaa927dc207a3f0c1c83ea1aa688cc418b194786afa4e8e3fb88789a9069bff0cf9e0b31fd9ec6d1135a251c92ee18251b8ba55a38dec45158d919c12efdd7580fb9a13c48171f6b78b9cf9e502ddfe008d4a8c11556572d2e3eb56153ed8fabdd38d7b8233fe538154b18b11beddd86071ed67d66e76508d685454231a951da370e0e19df8a25af7ae6ecb8d91ccb147ef4054d92a071cbdb0c4fca5f3ac763729451011440451415579e7b57d8c8d032e2ffb50ab167c489dc3fb9b9c7ca645a356514a8988202a8828a28288323e9239fdf643bcf07c0b1c1d7c5e706cbbf1e9c983ae96cfb95035638efcbad1a13b2cc92e814b646f4c4d7a1e7b6492b171c7b60e70d4ed5d5a71dcd25b7a71d61736ef83b55d51b57c30ef82e143852b0c1faacad56c1ddc32355d707bbeb8cab6ff01f23ad711d68e17be0000000049454e44ae426082000000ffff`
// Static41 returns static file
func Static41() (
int, // FileStart
int, // FileEnd
int, // CompressedStart
int, // CompressedEnd
string, // ContentHash
string, // CompressedHash
time.Time, // Time of creation
[]byte, // Data
) {
created, createErr := time.Parse(
time.RFC1123, "Wed, 07 Aug 2019 15:54:10 CST")
if createErr != nil {
panic(createErr)
}
data, dataErr := hex.DecodeString(rawStatic41Data)
rawStatic41Data = ""
if dataErr != nil {
panic(dataErr)
}
return 0, 828,
828, 1676,
"DPkhEZBiqsE=", "H8/B3536RVw=", created, data
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
package static_pages
// This file is part of Sshwifty Project
//
// Copyright (C) 2019 Rui NI (nirui@gmx.com)
//
// https://github.com/niruix/sshwifty
//
// This file is generated at Wed, 07 Aug 2019 15:54:10 CST
// by "go generate", DO NOT EDIT! Also, do not open this file, it maybe too large
// for your editor. You've been warned.
//
// This file may contain third-party binaries. See DEPENDENCES for detail.
import (
"time"
"encoding/hex"
)
var rawStatic47Data = `7b0a2020226e616d65223a202253737769667479222c0a20202273686f72745f6e616d65223a202253737769667479222c0a2020226465736372697074696f6e223a206e756c6c2c0a202022646972223a20226175746f222c0a2020226c616e67223a2022656e2d5553222c0a202022646973706c6179223a20227374616e64616c6f6e65222c0a2020226f7269656e746174696f6e223a2022616e79222c0a20202273746172745f75726c223a20222f3f686f6d6573637265656e3d31222c0a2020226261636b67726f756e645f636f6c6f72223a202223666666222c0a20202269636f6e73223a205b0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d33367833362e706e67222c0a2020202020202273697a6573223a20223336783336222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d2c0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d34387834382e706e67222c0a2020202020202273697a6573223a20223438783438222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d2c0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d37327837322e706e67222c0a2020202020202273697a6573223a20223732783732222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d2c0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d39367839362e706e67222c0a2020202020202273697a6573223a20223936783936222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d2c0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d313434783134342e706e67222c0a2020202020202273697a6573223a202231343478313434222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d2c0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d313932783139322e706e67222c0a2020202020202273697a6573223a202231393278313932222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d2c0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d323536783235362e706e67222c0a2020202020202273697a6573223a202232353678323536222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d2c0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d333834783338342e706e67222c0a2020202020202273697a6573223a202233383478333834222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d2c0a202020207b0a20202020202022737263223a2022616e64726f69642d6368726f6d652d353132783531322e706e67222c0a2020202020202273697a6573223a202235313278353132222c0a2020202020202274797065223a2022696d6167652f706e67220a202020207d0a20205d0a7d1f8b08000000000002ffac90cd6eab301085f73c85e5bb4d6e04180291aa3e44d45555452e1862d58c9131aa699477affc537553ab5d78e3c5f966fc8dce2d4308039d183e217c5edef9a037bcb3e172954a5f7e443d5b3ac567cd25e0138255081f736547e9aaa59f1314469b30d83f9dc32a5f6641379b2e9a424f8504e691549c81a6e1574ce1eb104d95beac4ad8f4f0789593d533060fb91f78a5dddba8e40afda59342ba23fe0dc3e029ef242cf8849e338410bab9d7feaa3a6fe995e4fdbebb2a39b17d599bb2fe3fc3e876fd20ff60761f3bf69deb6d76cdf0898eec60571cb9effee2218d214dc4e35822cfb130c722e2712c91a7ad4d1bebcdb1449e9c1093131231059acad516266f63ed059ac85554b529aa5883812672950d316513eb30d044ae2a2f4c95c73a0cf4775786d04b76ff040000ffff`
// Static47 returns static file
func Static47() (
int, // FileStart
int, // FileEnd
int, // CompressedStart
int, // CompressedEnd
string, // ContentHash
string, // CompressedHash
time.Time, // Time of creation
[]byte, // Data
) {
created, createErr := time.Parse(
time.RFC1123, "Wed, 07 Aug 2019 15:54:10 CST")
if createErr != nil {
panic(createErr)
}
data, dataErr := hex.DecodeString(rawStatic47Data)
rawStatic47Data = ""
if dataErr != nil {
panic(dataErr)
}
return 0, 1196,
1196, 1511,
"WdtgnBG8Sks=", "EKpkCbFqOGQ=", created, data
}

View File

@@ -0,0 +1,51 @@
package static_pages
// This file is part of Sshwifty Project
//
// Copyright (C) 2019 Rui NI (nirui@gmx.com)
//
// https://github.com/niruix/sshwifty
//
// This file is generated at Wed, 07 Aug 2019 15:54:10 CST
// by "go generate", DO NOT EDIT! Also, do not open this file, it maybe too large
// for your editor. You've been warned.
//
// This file may contain third-party binaries. See DEPENDENCES for detail.
import (
"time"
"encoding/hex"
)
var rawStatic48Data = `7b0a20202276657273696f6e223a2022312e30222c0a2020226e616d65223a202253737769667479222c0a2020226465736372697074696f6e223a206e756c6c2c0a20202269636f6e73223a207b0a20202020223630223a202266697265666f785f6170705f36307836302e706e67222c0a2020202022313238223a202266697265666f785f6170705f313238783132382e706e67222c0a2020202022353132223a202266697265666f785f6170705f353132783531322e706e67220a20207d2c0a202022646576656c6f706572223a207b0a20202020226e616d65223a206e756c6c2c0a202020202275726c223a206e756c6c0a20207d0a7d1f8b08000000000002ff5c8e410a83301444f79e62f8eb22f90145bc460f20626309a44948d4a614ef5ea29616b78f37c37b17002d2a44ed2cb5202e055d32b3fd4365708d4f3d4eaf1dde541c82f6d32edbd9980debc1d9482df21940b5c8c3510735bad4f5de77b548b528bdbd6f3700b16cce0ecb26b16cfead8ae5d9aa58a68ae56615c07a642dca38afc2afe1c8ff260234077380bc2bd60f000000ffff`
// Static48 returns static file
func Static48() (
int, // FileStart
int, // FileEnd
int, // CompressedStart
int, // CompressedEnd
string, // ContentHash
string, // CompressedHash
time.Time, // Time of creation
[]byte, // Data
) {
created, createErr := time.Parse(
time.RFC1123, "Wed, 07 Aug 2019 15:54:10 CST")
if createErr != nil {
panic(createErr)
}
data, dataErr := hex.DecodeString(rawStatic48Data)
rawStatic48Data = ""
if dataErr != nil {
panic(dataErr)
}
return 0, 250,
250, 410,
"1dt/vqnONiQ=", "S7HxwGVBXUY=", created, data
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
package static_pages
// This file is part of Sshwifty Project
//
// Copyright (C) 2019 Rui NI (nirui@gmx.com)
//
// https://github.com/niruix/sshwifty
//
// This file is generated at Wed, 07 Aug 2019 15:54:10 CST
// by "go generate", DO NOT EDIT! Also, do not open this file, it maybe too large
// for your editor. You've been warned.
//
// This file may contain third-party binaries. See DEPENDENCES for detail.
import (
"time"
"encoding/hex"
)
var rawStatic50Data = `7b2276657273696f6e223a332c22736f7572636573223a5b5d2c226e616d6573223a5b5d2c226d617070696e6773223a22222c2266696c65223a223732333762303734343439313032343039376433642e637373222c22736f75726365526f6f74223a22227d1f8b08000000000002ffaa562a4b2d2acecccf53b232d6512ace2f2d4a4e2d56b28a8ed551ca4bcc853173130b0a32f3d28b95ac94947494d232735295ac94cc8d8ccd930ccc4d4c4c2c0d0d8c4c0c2ccd538c53f4928b8b95600605e5e79780b4d402000000ffff`
// Static50 returns static file
func Static50() (
int, // FileStart
int, // FileEnd
int, // CompressedStart
int, // CompressedEnd
string, // ContentHash
string, // CompressedHash
time.Time, // Time of creation
[]byte, // Data
) {
created, createErr := time.Parse(
time.RFC1123, "Wed, 07 Aug 2019 15:54:10 CST")
if createErr != nil {
panic(createErr)
}
data, dataErr := hex.DecodeString(rawStatic50Data)
rawStatic50Data = ""
if dataErr != nil {
panic(dataErr)
}
return 0, 102,
102, 206,
"Pax14oQxroQ=", "aCysfJVxov4=", created, data
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
package static_pages
// This file is part of Sshwifty Project
//
// Copyright (C) 2019 Rui NI (nirui@gmx.com)
//
// https://github.com/niruix/sshwifty
//
// This file is generated at Wed, 07 Aug 2019 15:54:10 CST
// by "go generate", DO NOT EDIT! Also, do not open this file, it maybe too large
// for your editor. You've been warned.
//
// This file may contain third-party binaries. See DEPENDENCES for detail.
import (
"time"
"encoding/hex"
)
var rawStatic57Data = `2320446570656e64656e636573204f662053736877696674790a0a53736877696674792075736573206d616e792074686972642d706172747920636f6d706f6e656e74732e2054686f736520636f6d706f6e656e747320697320726571756972656420696e6f726465720a666f7220537368776966747920746f2066756e6374696f6e2e0a0a41206c697374206f66207573656420636f6d706f6e656e74732063616e20626520666f756e6420696e7369646520607061636b6167652e6a736f6e6020616e642060676f2e6d6f64602066696c652e0a0a4d616a6f7220646570656e64656e63657320696e636c756465733a0a0a232320466f722066726f6e742d656e64206170706c69636174696f6e0a0a2d205b5675655d2868747470733a2f2f7675656a732e6f7267292c204c6963656e73656420756e646572204d4954206c6963656e73650a2d205b426162656c5d2868747470733a2f2f626162656c6a732e696f2f292c204c6963656e73656420756e646572204d4954206c6963656e73650a2d205b585465726d2e6a735d2868747470733a2f2f787465726d6a732e6f72672f292c204c6963656e73656420756e646572204d4954206c6963656e73650a2d205b6e6f726d616c697a652e6373735d2868747470733a2f2f6769746875622e636f6d2f6e65636f6c61732f6e6f726d616c697a652e637373292c204c6963656e73656420756e646572204d4954206c6963656e73650a2d205b526f626f746f20666f6e745d2868747470733a2f2f656e2e77696b6970656469612e6f72672f77696b692f526f626f746f292c204c6963656e73656420756e64657220417061636865206c6963656e73652e0a20205061636b61676564206279205b43687269737469616e20486f66666d6569737465725d2868747470733a2f2f6769746875622e636f6d2f63686f66666d6569737465722f726f626f746f2d666f6e74666163652d626f776572292c204c6963656e73656420756e6465722041706163686520322e300a0a232320466f72206261636b2d656e64206170706c69636174696f6e0a0a2d20606769746875622e636f6d2f676f72696c6c612f776562736f636b6574602c204c6963656e73656420756e646572204253442d322d4361757365206c6963656e73650a2d2060676f6c616e672e6f72672f782f6e65742f70726f787960205b56696577206c6963656e73655d2868747470733a2f2f6769746875622e636f6d2f676f6c616e672f6e65742f626c6f622f6d61737465722f4c4943454e5345290a2d2060676f6c616e672e6f72672f782f63727970746f602c205b56696577206c6963656e73655d2868747470733a2f2f6769746875622e636f6d2f676f6c616e672f63727970746f2f626c6f622f6d61737465722f4c4943454e5345291f8b08000000000002ff8c914f6bdc3c1087effa1403b9bc81587ec931b7fc2b0d246d699650580a96a5b13dbbf28c2bc9dd753f7df19acdbaec96ed4d12f33cbf19cd053c6087ec902d46f85cc16b6c3654a541a9fd09fa88115ac303a48682cb3a13d20056da4e1839450d8b4622ce5e802204fcd1534007c4121c06554978b74312a87ab68984b552b7e02926906acc727391350c2542253d8fa6480ea1e88c5d9b1af52a0a1760d841518b6ec515509147add48b594900371b8dd8fade61bc51eae2023e48802a08a70cd981e93a4fd68ccd2895c1f2adc7efff352975f126cf7ff6b88a5a427d7905cf6491c70e7b7618e0e569017e7a1ab13b53a23f80e5785d454d929f45bf2d30b47a150ff4366168a7e0f3384b688da75fa86d9c396a4a4d5f6a2b6dce68c59b98ff5179d6fb554a1937259c0e5664bda13575e8c8ecda1b6ff9547a6cbced8c6d702fd50ae0cbb43d07e500cbfb26504c64183e4a55b54831613839816d0e0579d8a565636395b19895b2c1f0d7f06bfdfffbd64b63d7a7965ecca26a09e4bdc9375846b16b4cc591f9eef521bbceee4d1f71f661452dde70bdfb956dce98f22ec8762860f946b8d9179e1c6f22774ce9a5cc5bb31bf4f9e9fef1d3ebe3e591dc86a14b525cfdbb7a224eda7f030000ffff`
// Static57 returns static file
func Static57() (
int, // FileStart
int, // FileEnd
int, // CompressedStart
int, // CompressedEnd
string, // ContentHash
string, // CompressedHash
time.Time, // Time of creation
[]byte, // Data
) {
created, createErr := time.Parse(
time.RFC1123, "Wed, 07 Aug 2019 15:54:10 CST")
if createErr != nil {
panic(createErr)
}
data, dataErr := hex.DecodeString(rawStatic57Data)
rawStatic57Data = ""
if dataErr != nil {
panic(dataErr)
}
return 0, 1030,
1030, 1507,
"ZkFdpk2P7P0=", "tqmXI4zYPC8=", created, data
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,51 @@
package static_pages
// This file is part of Sshwifty Project
//
// Copyright (C) 2019 Rui NI (nirui@gmx.com)
//
// https://github.com/niruix/sshwifty
//
// This file is generated at Wed, 07 Aug 2019 15:54:11 CST
// by "go generate", DO NOT EDIT! Also, do not open this file, it maybe too large
// for your editor. You've been warned.
//
// This file may contain third-party binaries. See DEPENDENCES for detail.
import (
"time"
"encoding/hex"
)
var rawStatic72Data = `7b0a2020226170702e637373223a20222f6173736574732f3732333762303734343439313032343039376433642e637373222c0a2020226170702e6a73223a20222f6173736574732f3732333762303734343439313032343039376433642e6a73222c0a2020226170702e6373732e6d6170223a20222f6173736574732f3732333762303734343439313032343039376433642e6373732e6d6170222c0a202022686f6d652e637373223a20222f6173736574732f66373535363966386135666162303839336661373132643863306439633366652e776f666632222c0a20202273736877696674792e706e67223a20222f6173736574732f36393732663633653532343231363863633536333765323737313239326563302d6d616e69666573742e776562617070222c0a202022444550454e44454e4345532e6d64223a20222f6173736574732f444550454e44454e4345532e6d64222c0a2020224c4943454e53452e6d64223a20222f6173736574732f4c4943454e53452e6d64222c0a202022524541444d452e6d64223a20222f6173736574732f524541444d452e6d64222c0a202022636f6e6e6563742e637373223a20222f6173736574732f63353939333734653335306161373232356565336538613131346538643864372e737667222c0a202022636f6e6e656374696e672e737667223a20222f6173736574732f65613438353366663530396665356633353364353235383666636239346532302e737667222c0a202022696e6465782e68746d6c223a20222f6173736574732f696e6465782e68746d6c222c0a202022726f626f74732e747874223a20222f6173736574732f726f626f74732e747874220a7d1f8b08000000000002ff8cd0cd4ee3301405e07d9fa2ea7ac6756cdfd89edda8cd02092a449fc0b1ef6d53911f618b1621de1dc9806ab2eaf6e43b27577e5f2c972b374dccc7b8fab75cad5d8c98e25a0ba95bae9552b6e24271ab830c19fdf9699c6e289c0aef6364bd9b6efa4b86b9791c7b9c1f471aa0b6641c906bb9b1929cae44309e07eb25213b8f44e2ab1fe3f1dc517a63d37028376aab05d512412851d5c67ba8a546a17525ac40cffff66ee8086362676cddf47dcdb6796c76db66b769f6ac0fe5deec4bd6f7779b66b76f66b248b37a6afe6f1fe6e81a66e3c761409fe60fe1c15aa9154ae0ce69210051a27155a5d00413348baf875f03dd70c859b1814e199044c02d219004194080a9c9b756a1e0d78d6e087861c7d43f97fd22cdea656cc71459baa45215e9e2e3130000ffff`
// Static72 returns static file
func Static72() (
int, // FileStart
int, // FileEnd
int, // CompressedStart
int, // CompressedEnd
string, // ContentHash
string, // CompressedHash
time.Time, // Time of creation
[]byte, // Data
) {
created, createErr := time.Parse(
time.RFC1123, "Wed, 07 Aug 2019 15:54:11 CST")
if createErr != nil {
panic(createErr)
}
data, dataErr := hex.DecodeString(rawStatic72Data)
rawStatic72Data = ""
if dataErr != nil {
panic(dataErr)
}
return 0, 629,
629, 937,
"EJKjdtxdaNE=", "4ebrZ4o5Olg=", created, data
}

View File

@@ -0,0 +1,51 @@
package static_pages
// This file is part of Sshwifty Project
//
// Copyright (C) 2019 Rui NI (nirui@gmx.com)
//
// https://github.com/niruix/sshwifty
//
// This file is generated at Wed, 07 Aug 2019 15:54:11 CST
// by "go generate", DO NOT EDIT! Also, do not open this file, it maybe too large
// for your editor. You've been warned.
//
// This file may contain third-party binaries. See DEPENDENCES for detail.
import (
"time"
"encoding/hex"
)
var rawStatic73Data = `757365722d6167656e743a202a0a0a416c6c6f773a202f240a446973616c6c6f773a202f1f8b08000000000002ff2a2d4e2dd24d4c4fcd2bb152d0e2e272ccc9c92fb752d057e172c92c4e847200000000ffff`
// Static73 returns static file
func Static73() (
int, // FileStart
int, // FileEnd
int, // CompressedStart
int, // CompressedEnd
string, // ContentHash
string, // CompressedHash
time.Time, // Time of creation
[]byte, // Data
) {
created, createErr := time.Parse(
time.RFC1123, "Wed, 07 Aug 2019 15:54:11 CST")
if createErr != nil {
panic(createErr)
}
data, dataErr := hex.DecodeString(rawStatic73Data)
rawStatic73Data = ""
if dataErr != nil {
panic(dataErr)
}
return 0, 36,
36, 83,
"UqJ2rT+4q5w=", "Zxyoo2D9bZ0=", created, data
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long