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