|
|
|
|
package httpserver
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"b612.me/starcrypto"
|
|
|
|
|
"b612.me/starlog"
|
|
|
|
|
"b612.me/starnet"
|
|
|
|
|
"b612.me/staros"
|
|
|
|
|
"context"
|
|
|
|
|
_ "embed"
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"math"
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var version = "2.1.0"
|
|
|
|
|
|
|
|
|
|
type HttpServerCfgs func(cfg *HttpServerCfg)
|
|
|
|
|
|
|
|
|
|
type HttpServerCfg struct {
|
|
|
|
|
basicAuthUser string
|
|
|
|
|
basicAuthPwd string
|
|
|
|
|
envPath string
|
|
|
|
|
uploadFolder string
|
|
|
|
|
logpath string
|
|
|
|
|
indexFile string
|
|
|
|
|
cert string
|
|
|
|
|
key string
|
|
|
|
|
addr string
|
|
|
|
|
port string
|
|
|
|
|
page404 string
|
|
|
|
|
page403 string
|
|
|
|
|
page401 string
|
|
|
|
|
protectAuthPage []string
|
|
|
|
|
disableMIME bool
|
|
|
|
|
ctx context.Context
|
|
|
|
|
hooks []ServerHook
|
|
|
|
|
httpDebug bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ServerHook struct {
|
|
|
|
|
MatchType []string `json:"match_type"`
|
|
|
|
|
Url string `json:"url"`
|
|
|
|
|
Timeout int `json:"timeout"`
|
|
|
|
|
MaxHookLength int `json:"max_hook_length"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type HttpServer struct {
|
|
|
|
|
HttpServerCfg
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//go:embed bootstrap.css
|
|
|
|
|
var bootStrap []byte
|
|
|
|
|
|
|
|
|
|
//go:embed jquery.js
|
|
|
|
|
var jquery []byte
|
|
|
|
|
|
|
|
|
|
//go:embed upload.html
|
|
|
|
|
var uploadPage []byte
|
|
|
|
|
|
|
|
|
|
var htmlTitle string = `<!DOCTYPE html>
|
|
|
|
|
<html lang="zh_CN">
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<title>B612 Http Server %s</title>
|
|
|
|
|
<style>
|
|
|
|
|
* {
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
background-color: #f5f5f5;
|
|
|
|
|
font-family: Arial, sans-serif;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.container {
|
|
|
|
|
max-width: 960px;
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
padding: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h1 {
|
|
|
|
|
text-align: center;
|
|
|
|
|
margin-bottom: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
table {
|
|
|
|
|
width: 100%%;
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
margin-top: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th,
|
|
|
|
|
td {
|
|
|
|
|
padding: 12px;
|
|
|
|
|
text-align: left;
|
|
|
|
|
border-bottom: 1px solid #ddd;
|
|
|
|
|
position: relative;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th[data-sort]:before {
|
|
|
|
|
content: "▼";
|
|
|
|
|
display: inline-block;
|
|
|
|
|
height: 20px;
|
|
|
|
|
width: 20px;
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
vertical-align: middle;
|
|
|
|
|
position: absolute;
|
|
|
|
|
right: 0;
|
|
|
|
|
top: 50%%;
|
|
|
|
|
transform: translateY(-50%%);
|
|
|
|
|
opacity: 0.3;
|
|
|
|
|
transition: all 0.2s ease-in-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th[data-sort].asc:before {
|
|
|
|
|
content: "▲";
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th:hover:before {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.filename {
|
|
|
|
|
color: #007bff;
|
|
|
|
|
text-decoration: underline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.filetype {
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media screen and (max-width: 600px) {
|
|
|
|
|
table {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
<div class="container">
|
|
|
|
|
<h1>B612 Http Server - %s</h1>
|
|
|
|
|
<hr /><pre><h2> %s </h2></pre>%s
|
|
|
|
|
<table>
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th data-sort="name" class="asc">Name</th>
|
|
|
|
|
<th data-sort="modified">Modified</th>
|
|
|
|
|
<th data-sort="size">Size</th>
|
|
|
|
|
<th data-sort="type">Type</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>`
|
|
|
|
|
|
|
|
|
|
var htmlTail = ` </tbody>
|
|
|
|
|
</table>
|
|
|
|
|
<hr />
|
|
|
|
|
<pre>
|
|
|
|
|
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
|
|
|
|
|
</pre>
|
|
|
|
|
</div>
|
|
|
|
|
<script>
|
|
|
|
|
function sortTable(th, n) {
|
|
|
|
|
const table = document.querySelector('table');
|
|
|
|
|
const rows = table.rows;
|
|
|
|
|
let switching = true;
|
|
|
|
|
let shouldSwitch = false;
|
|
|
|
|
let direction = 'asc';
|
|
|
|
|
let switchcount = 0;
|
|
|
|
|
|
|
|
|
|
while (switching) {
|
|
|
|
|
switching = false;
|
|
|
|
|
let i;
|
|
|
|
|
for (i = 1; i < rows.length - 1; i++) {
|
|
|
|
|
shouldSwitch = false;
|
|
|
|
|
|
|
|
|
|
const x = rows[i].getElementsByTagName("td")[n];
|
|
|
|
|
const y = rows[i + 1].getElementsByTagName("td")[n];
|
|
|
|
|
|
|
|
|
|
let xValue, yValue;
|
|
|
|
|
if (n === 2) { // size sorting
|
|
|
|
|
if (x.innerText==="-") {
|
|
|
|
|
xValue=-1;
|
|
|
|
|
}else{
|
|
|
|
|
xValue = parseInt(x.innerText.split(' ')[0]);
|
|
|
|
|
}
|
|
|
|
|
if (y.innerText==="-") {
|
|
|
|
|
yValue=-1;
|
|
|
|
|
}else{
|
|
|
|
|
yValue = parseInt(y.innerText.split(' ')[0]);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
xValue = x.innerText.toLowerCase();
|
|
|
|
|
yValue = y.innerText.toLowerCase();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (direction === 'asc') {
|
|
|
|
|
if (xValue > yValue) {
|
|
|
|
|
shouldSwitch = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else if (direction === 'desc') {
|
|
|
|
|
if (xValue < yValue) {
|
|
|
|
|
shouldSwitch = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (shouldSwitch) {
|
|
|
|
|
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
|
|
|
|
|
switching = true;
|
|
|
|
|
switchcount++;
|
|
|
|
|
} else {
|
|
|
|
|
if (switchcount === 0 && direction === 'asc') {
|
|
|
|
|
direction = 'desc';
|
|
|
|
|
switching = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// update sort class
|
|
|
|
|
const ths = table.getElementsByTagName('th');
|
|
|
|
|
for (let i = 0; i < ths.length; i++) {
|
|
|
|
|
const currentTh = ths[i];
|
|
|
|
|
if (currentTh !== th) {
|
|
|
|
|
currentTh.classList.remove('asc');
|
|
|
|
|
} else {
|
|
|
|
|
currentTh.classList.toggle('asc');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// hide arrow on non-sorting columns
|
|
|
|
|
const sortableThs = table.querySelectorAll('thead th[data-sort]');
|
|
|
|
|
for (let i = 0; i < sortableThs.length; i++) {
|
|
|
|
|
const sortableTh = sortableThs[i];
|
|
|
|
|
if (sortableTh !== th) {
|
|
|
|
|
sortableTh.classList.remove('asc');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// add sorting event listener to thead
|
|
|
|
|
const ths = document.querySelectorAll('table th[data-sort]');
|
|
|
|
|
for (let i = 0; i < ths.length; i++) {
|
|
|
|
|
const th = ths[i];
|
|
|
|
|
th.addEventListener('click', () => {
|
|
|
|
|
const sortType = th.getAttribute('data-sort');
|
|
|
|
|
let columnIndex;
|
|
|
|
|
switch (sortType) {
|
|
|
|
|
case 'name':
|
|
|
|
|
columnIndex = 0;
|
|
|
|
|
break;
|
|
|
|
|
case 'modified':
|
|
|
|
|
columnIndex = 1;
|
|
|
|
|
break;
|
|
|
|
|
case 'size':
|
|
|
|
|
columnIndex = 2;
|
|
|
|
|
break;
|
|
|
|
|
case 'type':
|
|
|
|
|
columnIndex = 3;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
sortTable(th, columnIndex);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
|
|
|
|
</html>
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
func WithHooks(hooks []ServerHook) HttpServerCfgs {
|
|
|
|
|
return func(cfg *HttpServerCfg) {
|
|
|
|
|
for k, v := range hooks {
|
|
|
|
|
if v.MaxHookLength == 0 {
|
|
|
|
|
hooks[k].MaxHookLength = 1024 * 1024
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
cfg.hooks = hooks
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WithTLSCert(cert, key string) HttpServerCfgs {
|
|
|
|
|
return func(cfg *HttpServerCfg) {
|
|
|
|
|
cfg.key = key
|
|
|
|
|
cfg.cert = cert
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func WithUploadFolder(path string) HttpServerCfgs {
|
|
|
|
|
return func(cfg *HttpServerCfg) {
|
|
|
|
|
cfg.uploadFolder = path
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewHttpServer(addr, port, path string, opts ...HttpServerCfgs) *HttpServer {
|
|
|
|
|
var server = HttpServer{
|
|
|
|
|
HttpServerCfg: HttpServerCfg{
|
|
|
|
|
addr: addr,
|
|
|
|
|
port: port,
|
|
|
|
|
envPath: path,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
|
opt(&server.HttpServerCfg)
|
|
|
|
|
}
|
|
|
|
|
return &server
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) Run(ctx context.Context) error {
|
|
|
|
|
h.ctx = ctx
|
|
|
|
|
server := http.Server{
|
|
|
|
|
Addr: h.addr + ":" + h.port,
|
|
|
|
|
Handler: h,
|
|
|
|
|
}
|
|
|
|
|
go func() {
|
|
|
|
|
select {
|
|
|
|
|
case <-h.ctx.Done():
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
|
|
|
defer cancel()
|
|
|
|
|
server.Shutdown(ctx)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
if h.logpath != "" {
|
|
|
|
|
starlog.SetLogFile(h.logpath, starlog.Std, true)
|
|
|
|
|
}
|
|
|
|
|
netcards, err := net.Interfaces()
|
|
|
|
|
if err == nil {
|
|
|
|
|
for _, v := range netcards {
|
|
|
|
|
if strings.Contains(v.Flags.String(), "up") {
|
|
|
|
|
addrs, err := v.Addrs()
|
|
|
|
|
if err == nil {
|
|
|
|
|
for _, ip := range addrs {
|
|
|
|
|
starlog.Cyan("Name:%s\tIP:%s\n", v.Name, ip)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
h.envPath, err = filepath.Abs(h.envPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
starlog.Errorln("Failed to get abs path of", h.envPath)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
uconn, err := net.Dial("udp", "106.55.44.79:80")
|
|
|
|
|
if err == nil {
|
|
|
|
|
schema := "http://"
|
|
|
|
|
if h.cert != "" {
|
|
|
|
|
schema = "https://"
|
|
|
|
|
}
|
|
|
|
|
starlog.Infof("Visit: %s%s:%s\n", schema, uconn.LocalAddr().(*net.UDPAddr).IP.String(), h.port)
|
|
|
|
|
uconn.Close()
|
|
|
|
|
}
|
|
|
|
|
starlog.Infoln("Listening on " + h.addr + ":" + h.port)
|
|
|
|
|
if h.cert == "" {
|
|
|
|
|
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if err := server.ListenAndServeTLS(h.cert, h.key); err != http.ErrServerClosed {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
h.Listen(w, r)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) Page404(w http.ResponseWriter) {
|
|
|
|
|
w.WriteHeader(404)
|
|
|
|
|
if h.page404 != "" {
|
|
|
|
|
data, err := os.ReadFile(h.page404)
|
|
|
|
|
if err == nil {
|
|
|
|
|
w.Write(data)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">404 NOT FOUND</h1><hr ></body></html>`))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) Page403(w http.ResponseWriter) {
|
|
|
|
|
w.WriteHeader(403)
|
|
|
|
|
if h.page403 != "" {
|
|
|
|
|
data, err := os.ReadFile(h.page403)
|
|
|
|
|
if err == nil {
|
|
|
|
|
w.Write(data)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">403 Forbidden</h1><hr ></body></html>`))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) Page401(w http.ResponseWriter) {
|
|
|
|
|
w.WriteHeader(401)
|
|
|
|
|
if h.page401 != "" {
|
|
|
|
|
data, err := os.ReadFile(h.page401)
|
|
|
|
|
if err == nil {
|
|
|
|
|
w.Write(data)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
w.Write([]byte(`
|
|
|
|
|
<html>
|
|
|
|
|
<head><title>401 Authorization Required</title></head>
|
|
|
|
|
<body>
|
|
|
|
|
<center><h1>401 Authorization Required</h1></center>
|
|
|
|
|
<hr><center>B612 HTTP SERVER</center>
|
|
|
|
|
</body>
|
|
|
|
|
</html>`))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) GiveBasicAuth(w http.ResponseWriter) {
|
|
|
|
|
w.Header().Set("WWW-Authenticate", ` Basic realm="Please Enter Passwd"`)
|
|
|
|
|
h.Page401(w)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) BasicAuth(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request) bool {
|
|
|
|
|
if h.basicAuthPwd != "" {
|
|
|
|
|
if len(h.protectAuthPage) != 0 {
|
|
|
|
|
for _, v := range h.protectAuthPage {
|
|
|
|
|
if !(strings.Index(r.URL.Path, v) == 0 || strings.Contains(r.URL.RawQuery, v)) {
|
|
|
|
|
return true
|
|
|
|
|
} else {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
authHeader := strings.TrimSpace(r.Header.Get("Authorization"))
|
|
|
|
|
if len(authHeader) == 0 {
|
|
|
|
|
log.Noticeln("No Authed! Get Path is", r.URL.Path, r.RemoteAddr)
|
|
|
|
|
h.GiveBasicAuth(w)
|
|
|
|
|
return false
|
|
|
|
|
} else {
|
|
|
|
|
userAuth := base64.StdEncoding.EncodeToString([]byte(h.basicAuthUser + ":" + h.basicAuthPwd))
|
|
|
|
|
authStr := strings.Split(authHeader, " ")
|
|
|
|
|
if strings.TrimSpace(authStr[1]) != userAuth || strings.ToLower(strings.TrimSpace(authStr[0])) != "basic" {
|
|
|
|
|
log.Noticeln("Auth Failed! Get Path is", r.URL.Path, r.RemoteAddr, "pwd enter is", authHeader)
|
|
|
|
|
h.GiveBasicAuth(w)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
log.Infof("Path %s Authoried by %s:%s\n", r.URL.Path, r.RemoteAddr, authHeader)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) SetUpload(w http.ResponseWriter, r *http.Request, path string) bool {
|
|
|
|
|
if h.uploadFolder != "" {
|
|
|
|
|
if r.URL.Query().Get("bootstrap") == "true" {
|
|
|
|
|
w.Header().Set("Content-Type", "text/css")
|
|
|
|
|
w.Write(bootStrap)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if r.URL.Query().Get("jquery") == "true" {
|
|
|
|
|
w.Header().Set("Content-Type", "application/javascript")
|
|
|
|
|
w.Write(jquery)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if len(r.URL.Query()["upload"]) != 0 {
|
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
|
w.Write(uploadPage)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) debugMode(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
w.Header().Set("Content-Type", "text/html")
|
|
|
|
|
w.WriteHeader(200)
|
|
|
|
|
html := `<html><head><meta charset="utf-8"><title>B612 Http Server</title></head><body><h1 "style="text-align: center;">Debug Mode</h1><hr >%s</body></html>`
|
|
|
|
|
resp := "<h2>Url</h2>"
|
|
|
|
|
resp += "<p>" + r.Method + " " + r.URL.Path + "</p>"
|
|
|
|
|
resp += "<p> query: " + r.URL.RawQuery + "</p>"
|
|
|
|
|
resp += "<p> fragment: " + r.URL.Fragment + "</p>"
|
|
|
|
|
resp += "<p> FullUrl: " + r.URL.String() + "</p>"
|
|
|
|
|
resp += "<h2>Query</h2>"
|
|
|
|
|
for k, v := range r.URL.Query() {
|
|
|
|
|
resp += fmt.Sprintf("<p>%s:%s</p>", k, v)
|
|
|
|
|
}
|
|
|
|
|
resp += "<h2>Header</h2>"
|
|
|
|
|
for key, val := range r.Header {
|
|
|
|
|
for _, v := range val {
|
|
|
|
|
resp += fmt.Sprintf("<p>%s:%s</p>", key, v)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
resp += "<h2>Cookie</h2>"
|
|
|
|
|
for _, c := range r.Cookies() {
|
|
|
|
|
resp += fmt.Sprintf("<p>%s:%s</p>", c.Name, c.Value)
|
|
|
|
|
}
|
|
|
|
|
resp += "<h2>RemoteAddr</h2>"
|
|
|
|
|
resp += "<p>" + r.RemoteAddr + "</p>"
|
|
|
|
|
resp += "<h2>Proto</h2>"
|
|
|
|
|
resp += "<p>" + r.Proto + "</p>"
|
|
|
|
|
w.Write([]byte(fmt.Sprintf(html, resp)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
log := starlog.Std.NewFlag()
|
|
|
|
|
log.SetShowFuncName(false)
|
|
|
|
|
log.SetShowOriginFile(false)
|
|
|
|
|
w.Header().Set("X-Powered-By", "B612.ME")
|
|
|
|
|
w.Header().Set("Server", "B612/"+version)
|
|
|
|
|
if !h.BasicAuth(log, w, r) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
path := r.URL.Path
|
|
|
|
|
ua := r.Header.Get("User-Agent")
|
|
|
|
|
if h.httpDebug {
|
|
|
|
|
log.Infof("debug mode:%s %s From %s %s\n", r.Method, path, r.RemoteAddr, ua)
|
|
|
|
|
h.debugMode(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if h.uploadFolder != "" && path == "/recv" && len(r.URL.Query()["upload"]) != 0 {
|
|
|
|
|
h.uploadFile(w, r)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
fullpath := filepath.Clean(filepath.Join(h.envPath, path))
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
//security check
|
|
|
|
|
if fullpath != h.envPath && !strings.HasPrefix(fullpath, h.envPath) {
|
|
|
|
|
log.Warningf("Invalid Path %s IP:%s Fullpath:%s\n", path, r.RemoteAddr, fullpath)
|
|
|
|
|
h.Page403(w)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if h.indexFile != "" && staros.IsFolder(fullpath) {
|
|
|
|
|
if staros.Exists(filepath.Join(fullpath, h.indexFile)) {
|
|
|
|
|
fullpath = filepath.Join(fullpath, h.indexFile)
|
|
|
|
|
path = filepath.Join(path, h.indexFile)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
now := time.Now()
|
|
|
|
|
if h.SetUpload(w, r, path) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
switch r.Method {
|
|
|
|
|
case "OPTIONS", "HEAD":
|
|
|
|
|
err := h.BuildHeader(w, r, fullpath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Warningf("%s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds(), err)
|
|
|
|
|
} else {
|
|
|
|
|
log.Infof("%s %s From %s %s %.2fs \n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
|
|
|
|
|
}
|
|
|
|
|
case "GET":
|
|
|
|
|
err := h.BuildHeader(w, r, fullpath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Warningf("GET Header Build Failed Path:%s IP:%s Err:%v\n", path, r.RemoteAddr, err)
|
|
|
|
|
}
|
|
|
|
|
err = h.ResponseGet(log, w, r, fullpath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Warningf("%s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds(), err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
log.Infof("%s %s From %s %s %.2fs\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
|
|
|
|
|
default:
|
|
|
|
|
log.Errorf("Invalid %s %s From %s %s %.2fs\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) CalcRange(r *http.Request) (int64, int64) {
|
|
|
|
|
var rangeStart, rangeEnd int64
|
|
|
|
|
rangeStart, rangeEnd = -1, -1
|
|
|
|
|
for k, v := range r.Header {
|
|
|
|
|
if strings.ToLower(k) == "range" {
|
|
|
|
|
if strings.Contains(v[0], "bytes=") {
|
|
|
|
|
v[0] = strings.Replace(v[0], "bytes=", "", -1)
|
|
|
|
|
}
|
|
|
|
|
data := strings.Split(v[0], "-")
|
|
|
|
|
if len(data) == 0 {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
|
|
|
|
|
if len(data) > 1 {
|
|
|
|
|
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
|
|
|
|
|
}
|
|
|
|
|
//w.WriteHeader(206) //206 支持断点续传
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return rangeStart, rangeEnd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpath string) error {
|
|
|
|
|
if r.Method == "OPTIONS" {
|
|
|
|
|
w.Header().Set("Allow", "OPTIONS,GET,HEAD")
|
|
|
|
|
w.Header().Set("Content-Length", "0")
|
|
|
|
|
}
|
|
|
|
|
w.Header().Set("Date", strings.ReplaceAll(time.Now().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST"), "UTC", "GMT"))
|
|
|
|
|
if staros.IsFolder(fullpath) {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
mime := h.MIME(fullpath)
|
|
|
|
|
if h.disableMIME || mime == "" {
|
|
|
|
|
w.Header().Set("Content-Type", "application/download")
|
|
|
|
|
w.Header().Set("Content-Disposition", "attachment;filename="+filepath.Base(fullpath))
|
|
|
|
|
w.Header().Set("Content-Transfer-Encoding", "binary")
|
|
|
|
|
} else {
|
|
|
|
|
w.Header().Set("Content-Type", mime)
|
|
|
|
|
}
|
|
|
|
|
if staros.Exists(fullpath) {
|
|
|
|
|
finfo, err := os.Stat(fullpath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
w.WriteHeader(502)
|
|
|
|
|
w.Write([]byte("Failed to Read " + fullpath + ",reason is " + err.Error()))
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
w.Header().Set("Accept-Ranges", "bytes")
|
|
|
|
|
w.Header().Set("ETag", starcrypto.Md5Str([]byte(finfo.ModTime().String())))
|
|
|
|
|
w.Header().Set("Last-Modified", strings.ReplaceAll(finfo.ModTime().UTC().Format("Mon, 2 Jan 2006 15:04:05 MST"), "UTC", "GMT"))
|
|
|
|
|
if r.Method != "OPTIONS" {
|
|
|
|
|
if _, ok := h.willHook(fullpath); ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
|
|
|
|
|
start, end := h.CalcRange(r)
|
|
|
|
|
if start != -1 {
|
|
|
|
|
if end == -1 {
|
|
|
|
|
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(finfo.Size(), 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
|
|
|
|
|
//w.Header().Set("Content-Length", strconv.FormatInt(fpinfo.Size()-rangeStart, 10))
|
|
|
|
|
} else {
|
|
|
|
|
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
|
|
|
|
|
//w.Header().Set("Content-Length", strconv.FormatInt(1+rangeEnd-rangeStart, 10))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) willHook(fullpath string) (ServerHook, bool) {
|
|
|
|
|
finfo, err := os.Stat(fullpath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return ServerHook{}, false
|
|
|
|
|
}
|
|
|
|
|
if finfo.Size() < 1024*1024*10 && len(h.hooks) > 0 {
|
|
|
|
|
ext := h.GetExt(fullpath)
|
|
|
|
|
for _, hk := range h.hooks {
|
|
|
|
|
for _, e := range hk.MatchType {
|
|
|
|
|
if e == ext {
|
|
|
|
|
return hk, true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ServerHook{}, false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
|
|
|
|
|
if staros.IsFolder(fullpath) {
|
|
|
|
|
return h.getFolder(log, w, r, fullpath)
|
|
|
|
|
}
|
|
|
|
|
return h.getFile(log, w, r, fullpath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
|
|
|
|
|
dir, err := ioutil.ReadDir(fullpath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Read Folder %s failed:%v\n", fullpath, err)
|
|
|
|
|
w.WriteHeader(403)
|
|
|
|
|
if r.Method == "HEAD" {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
w.Write([]byte("<h1>Cannot Access!</h1>"))
|
|
|
|
|
}
|
|
|
|
|
if r.Method != "GET" {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
var upload string
|
|
|
|
|
if h.uploadFolder != "" {
|
|
|
|
|
upload = `<a href=/b612?upload=true>Upload Web Page Is Openned!</a>`
|
|
|
|
|
}
|
|
|
|
|
w.Write([]byte(fmt.Sprintf(htmlTitle, r.URL.Path, version, "Index of "+r.URL.Path, upload)))
|
|
|
|
|
if r.URL.Path != "/" {
|
|
|
|
|
p := r.URL.Path
|
|
|
|
|
if p[len(p)-1:] != "/" {
|
|
|
|
|
p += "/"
|
|
|
|
|
}
|
|
|
|
|
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
|
|
|
|
|
p+"..", "../", "-", "-", "上层文件夹")))
|
|
|
|
|
}
|
|
|
|
|
if r.URL.Path == "/" {
|
|
|
|
|
r.URL.Path = ""
|
|
|
|
|
} else if r.URL.Path[len(r.URL.Path)-1:] == "/" {
|
|
|
|
|
r.URL.Path = r.URL.Path[0 : len(r.URL.Path)-1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, v := range dir {
|
|
|
|
|
if v.Name() != "." || v.Name() != ".." {
|
|
|
|
|
if !v.IsDir() {
|
|
|
|
|
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
|
|
|
|
|
r.URL.Path+"/"+v.Name(), v.Name(), v.ModTime().Format("2006-01-02 15:04:05"), fmt.Sprintf("%d (%s)", v.Size(), h.trimSize(v.Size())), h.FileType(v.Name()))))
|
|
|
|
|
} else {
|
|
|
|
|
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
|
|
|
|
|
r.URL.Path+"/"+v.Name(), v.Name()+"/", v.ModTime().Format("2006-01-02 15:04:05"), "-", "文件夹")))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
w.Write([]byte(htmlTail))
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
|
|
|
|
|
if !staros.Exists(fullpath) {
|
|
|
|
|
h.Page404(w)
|
|
|
|
|
return errors.New("File Not Found! 404 ERROR")
|
|
|
|
|
}
|
|
|
|
|
//starlog.Debugln(r.Header)
|
|
|
|
|
startRange, endRange := h.CalcRange(r)
|
|
|
|
|
fp, err := os.Open(fullpath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Failed to open file %s,reason:%v\n", r.URL.Path, err)
|
|
|
|
|
w.WriteHeader(502)
|
|
|
|
|
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer fp.Close()
|
|
|
|
|
var transferData int
|
|
|
|
|
defer func() {
|
|
|
|
|
if transferData != 0 {
|
|
|
|
|
var tani string
|
|
|
|
|
tani = fmt.Sprintf("%v Byte", transferData)
|
|
|
|
|
if f64 := float64(transferData) / 1024; f64 > 1 {
|
|
|
|
|
tani = fmt.Sprintf("%v KiB", f64)
|
|
|
|
|
if f64 = float64(f64) / 1024; f64 > 1 {
|
|
|
|
|
tani = fmt.Sprintf("%v MiB", f64)
|
|
|
|
|
if f64 = float64(f64) / 1024; f64 > 1 {
|
|
|
|
|
tani = fmt.Sprintf("%v GiB", f64)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log.Infof("Tranfered File %s %d bytes (%s) to remote %v\n", r.URL.Path,
|
|
|
|
|
transferData, tani, r.RemoteAddr)
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
if startRange == -1 {
|
|
|
|
|
hook, needCurl := h.willHook(fullpath)
|
|
|
|
|
if !needCurl {
|
|
|
|
|
w.WriteHeader(200)
|
|
|
|
|
for {
|
|
|
|
|
buf := make([]byte, 1048576)
|
|
|
|
|
n, err := fp.Read(buf)
|
|
|
|
|
if n != 0 {
|
|
|
|
|
ns, err := w.Write(buf[0:n])
|
|
|
|
|
transferData += ns
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
log.Errorln("Read File %s Failed:%v\n", fullpath, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
data, err := os.ReadFile(fullpath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
w.WriteHeader(502)
|
|
|
|
|
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
|
|
|
|
|
log.Errorf("Read File %s Failed:%v\n", fullpath, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
b64 := base64.StdEncoding.EncodeToString(data)
|
|
|
|
|
req, err := starnet.Curl(starnet.NewRequests(hook.Url, starnet.BuildPostForm(map[string]string{
|
|
|
|
|
"data": b64,
|
|
|
|
|
"ip": r.RemoteAddr,
|
|
|
|
|
}),
|
|
|
|
|
"POST",
|
|
|
|
|
starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond)))
|
|
|
|
|
if err != nil || len(req.RecvData) == 0 {
|
|
|
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
|
|
|
|
|
w.WriteHeader(200)
|
|
|
|
|
ns, err := w.Write(data)
|
|
|
|
|
transferData += ns
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
w.WriteHeader(200)
|
|
|
|
|
w.Header().Set("Content-Length", strconv.Itoa(len(req.RecvData)))
|
|
|
|
|
ns, err := w.Write(req.RecvData)
|
|
|
|
|
transferData += ns
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
log.Debugf("206 transfer mode for %v %v start %v end %v\n", r.URL.Path, r.RemoteAddr, startRange, endRange)
|
|
|
|
|
w.WriteHeader(206)
|
|
|
|
|
fp.Seek(int64(startRange), 0)
|
|
|
|
|
count := startRange
|
|
|
|
|
for {
|
|
|
|
|
buf := make([]byte, 1048576)
|
|
|
|
|
n, err := fp.Read(buf)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err == io.EOF {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
log.Errorf("Read File %s Failed:%v\n", r.URL.Path, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if endRange == -1 {
|
|
|
|
|
ns, err := w.Write(buf[0:n])
|
|
|
|
|
transferData += ns
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("Transfer File %s to Remote Failed:%v\n", r.URL.Path, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if count > endRange {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
writeNum := n
|
|
|
|
|
if count+int64(n) > endRange {
|
|
|
|
|
writeNum = int(endRange - count + 1)
|
|
|
|
|
}
|
|
|
|
|
ns, err := w.Write(buf[0:writeNum])
|
|
|
|
|
transferData += ns
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorln("Transfer Error:", err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
count += int64(n)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) trimSize(size int64) string {
|
|
|
|
|
var tani string
|
|
|
|
|
tani = fmt.Sprintf("%v Byte", size)
|
|
|
|
|
if f64 := float64(size) / 1024; f64 > 1 {
|
|
|
|
|
tani = fmt.Sprintf("%.3f KiB", math.Trunc(f64*1e3+0.5)*1e-3)
|
|
|
|
|
if f64 = float64(f64) / 1024; f64 > 1 {
|
|
|
|
|
tani = fmt.Sprintf("%.3f MiB", math.Trunc(f64*1e3+0.5)*1e-3)
|
|
|
|
|
if f64 = float64(f64) / 1024; f64 > 1 {
|
|
|
|
|
tani = fmt.Sprintf("%.3f GiB", math.Trunc(f64*1e3+0.5)*1e-3)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return tani
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *HttpServer) uploadFile(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
if r.Method != "POST" {
|
|
|
|
|
w.WriteHeader(200)
|
|
|
|
|
w.Write([]byte("USE POST METHOD!"))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
r.ParseMultipartForm(10485760)
|
|
|
|
|
file, handler, err := r.FormFile("victorique")
|
|
|
|
|
if err != nil {
|
|
|
|
|
starlog.Errorf("Parse File From Form Failed:%v\n", err)
|
|
|
|
|
w.WriteHeader(502)
|
|
|
|
|
w.Write([]byte(err.Error()))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer file.Close()
|
|
|
|
|
starlog.Noticef("Uploading %s From %s\n", handler.Filename, r.RemoteAddr)
|
|
|
|
|
os.MkdirAll(filepath.Join(h.envPath, h.uploadFolder), 0755)
|
|
|
|
|
f, err := os.OpenFile(filepath.Join(h.uploadFolder, handler.Filename), os.O_WRONLY|os.O_CREATE, 0755)
|
|
|
|
|
if err != nil {
|
|
|
|
|
starlog.Errorf("Open Local File %s Form %v Failed:%v\n", handler.Filename, r.RemoteAddr, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
_, err = io.Copy(f, file)
|
|
|
|
|
if err != nil {
|
|
|
|
|
starlog.Errorf("Write File %s Form %v Failed:%v\n", handler.Filename, r.RemoteAddr, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
starlog.Infof("Write File %s Form %v Finished\n", handler.Filename, r.RemoteAddr)
|
|
|
|
|
fmt.Fprintf(w, `<html><body><p>%v</p><h2><a href="/b612?upload=true">Return To Web Page</a></h2></body></html>`, handler.Header)
|
|
|
|
|
}
|