initial commit
This commit is contained in:
commit
85b0d73faa
29
LICENSE.md
Normal file
29
LICENSE.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2018, hybris
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
72
serve.go
Normal file
72
serve.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2018 hybris. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
// simple wrapper around http.Fileserver and http.ServeFile
|
||||||
|
package serve
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"github.com/kpango/glg"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func Serve(path_tainted string, ip_tainted string, port_tainted int) {
|
||||||
|
if (os.Geteuid() == 0) {
|
||||||
|
glg.Warn("running as root")
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err_path := validatePath(path_tainted)
|
||||||
|
ip, err_ip := validateAddress(ip_tainted)
|
||||||
|
port, err_port := validatePort(port_tainted)
|
||||||
|
|
||||||
|
listen := strings.Join([]string{ip,strconv.Itoa(port)}, ":")
|
||||||
|
|
||||||
|
if (err_path == nil && err_ip == nil && err_port == nil) {
|
||||||
|
fi, _ := os.Stat(path)
|
||||||
|
switch mode := fi.Mode(); {
|
||||||
|
case mode.IsDir():
|
||||||
|
serveDirectory(path,listen)
|
||||||
|
break
|
||||||
|
case mode.IsRegular():
|
||||||
|
serveFile(path,listen)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDirectory will start a http.FileServer for the given path on the given listen address
|
||||||
|
// expects `path` to be a valid directory
|
||||||
|
func serveDirectory(path,listen string) {
|
||||||
|
glg.Infof("serving directory %s on %s",path,listen)
|
||||||
|
if (strings.Compare(path,"/") == 0) {
|
||||||
|
glg.Warn("you are serving your whole filesystem right now, use with care")
|
||||||
|
}
|
||||||
|
panic(http.ListenAndServe(listen, loggingHandler(http.FileServer(http.Dir(path)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom handler to achieve access logging for http.FileServer
|
||||||
|
func loggingHandler(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
glg.Infof("Access: from: %s method: %s full_path: %s%s",r.RemoteAddr,r.Method,r.Host,r.URL.Path)
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeFile will start http.ListenAndServe and answer request on "/" with the file from the given path
|
||||||
|
// expects `path` to be a valid file
|
||||||
|
func serveFile(path,listen string) {
|
||||||
|
glg.Infof("serving file '%s' on '%s'",path,listen)
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request){
|
||||||
|
glg.Infof("Access: from: %s method: %s full_path: %s%s",r.RemoteAddr,r.Method,r.Host,r.URL.Path)
|
||||||
|
var header_string = "attachment; filename="
|
||||||
|
header_string += filepath.Base(path)
|
||||||
|
w.Header().Set("Content-Disposition", header_string)
|
||||||
|
http.ServeFile(w, r, path)
|
||||||
|
})
|
||||||
|
panic(http.ListenAndServe(listen,nil))
|
||||||
|
}
|
71
validate.go
Normal file
71
validate.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2018 hybris. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE.md file.
|
||||||
|
|
||||||
|
package serve
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"net"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"path/filepath"
|
||||||
|
"github.com/kpango/glg"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidatePath validates if the path exists and returns the full absolute path of the given path.
|
||||||
|
// if no path was provided (e.g. empty string), it will return the current working directory.
|
||||||
|
// exit with error if path is not valid or does not exist
|
||||||
|
func validatePath(path_tainted string) (string, error) {
|
||||||
|
if path_tainted == "" {
|
||||||
|
glg.Info("no directory specified, serving current working directory")
|
||||||
|
return filepath.Abs(filepath.Dir(os.Args[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(path_tainted); os.IsNotExist(err) {
|
||||||
|
glg.Errorf("file or directory does not exist: %s",path_tainted)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepath.IsAbs(path_tainted) {
|
||||||
|
return path_tainted, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return filepath.Abs(path_tainted)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAddress will check if a provided string is a valid ipv4/6 address and return it as string.
|
||||||
|
// if will return ipv6 addresses with surrounding brackets.(Example: "::" => "[::]")
|
||||||
|
func validateAddress(address_tainted string) (string, error) {
|
||||||
|
if address_tainted == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var address = net.ParseIP(address_tainted)
|
||||||
|
if address == nil {
|
||||||
|
glg.Errorf("%s is not a valid ipv4 or ipv6 address",address_tainted)
|
||||||
|
return "", errors.New("not a valid ipv4 or ipv6 address")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address.To4() == nil) {
|
||||||
|
return strings.Join([]string{"[","]"},address.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return address.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatePort will check if a given port is in valid port range.
|
||||||
|
// returns the input if port is valid
|
||||||
|
// exits with errror if port is out of range, or port <1024 is called without priviliges
|
||||||
|
func validatePort(port_tainted int) (int, error) {
|
||||||
|
if(port_tainted < 0 || port_tainted > 65535) {
|
||||||
|
glg.Errorf("port %d is invalid, please provide a port in range 0-65535",port_tainted)
|
||||||
|
return 0, errors.New("Port is out of range, please provide a port in range 0-65535")
|
||||||
|
}
|
||||||
|
if(os.Geteuid() != 0 && port_tainted < 1024) {
|
||||||
|
glg.Errorf("cannot bind to port %d without root priviliges",port_tainted)
|
||||||
|
return 0, errors.New("Ports below 1024 can only be bind by root. You are not root.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return port_tainted, nil
|
||||||
|
}
|
79
validate_test.go
Normal file
79
validate_test.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package serve
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidatePath(t *testing.T) {
|
||||||
|
invalidPaths := []string{"://","/'.'/"}
|
||||||
|
validPaths := []string{".","./","/",""}
|
||||||
|
|
||||||
|
for _, path := range invalidPaths {
|
||||||
|
_, err := validatePath(path)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Path '%s' is supposed to be invalid.", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range validPaths {
|
||||||
|
_, err := validatePath(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
t.Errorf("Path '%s' is supposed to be valid.", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateAddress(t *testing.T) {
|
||||||
|
invalidIPs := []string{"string",":::::1::::","1.2.3.4.5","300.200.100.000","localhost"}
|
||||||
|
validIPs := []string{"::","0.0.0.0","","::1"}
|
||||||
|
|
||||||
|
for _, ip := range invalidIPs {
|
||||||
|
_, err := validateAddress(ip)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("IP '%s' is supposed to be invalid.", ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ip := range validIPs {
|
||||||
|
_, err := validateAddress(ip)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("IP '%s' is supposed to be valid.", ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidatePort(t *testing.T) {
|
||||||
|
// test valid ports
|
||||||
|
for i := 1024; i < 65536; i++ {
|
||||||
|
_, err := validatePort(i)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Port %d should be valid",i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test admin ports
|
||||||
|
for j := 1; j < 1024; j++ {
|
||||||
|
_, err := validatePort(j)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Port %d should not be valid",j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test negative ports
|
||||||
|
for k := 0; k > -65536; k-- {
|
||||||
|
_, err := validatePort(k)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Port %d should not be valid",k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// test ports above 65536
|
||||||
|
for l := 65536; l < 131072; l++ {
|
||||||
|
_, err := validatePort(l)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Port %d should not be valid",l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user