1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-09-03 17:36:23 -07:00
gochan/pkg/gcutil/util.go

281 lines
7.2 KiB
Go
Raw Normal View History

package gcutil
import (
"crypto/md5"
"crypto/sha1" // skipcq GSC-G505
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/netip"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/aquilax/tripcode"
"golang.org/x/crypto/bcrypt"
x_html "golang.org/x/net/html"
)
const (
// DefaultMaxAge is used for cookies that have an invalid or unset max age (default is 1 month)
DefaultMaxAge = 60 * 60 * 24 * 31
)
var (
// ErrNotImplemented should be used for unimplemented functionality when necessary, not for bugs
ErrNotImplemented = errors.New("not implemented")
)
// BcryptSum generates and returns a checksum using the bcrypt hashing function
func BcryptSum(str string) string {
2023-12-19 14:33:40 -08:00
digest, err := bcrypt.GenerateFromPassword([]byte(str), 10)
if err == nil {
return string(digest)
}
return ""
}
// Md5Sum generates and returns a checksum using the MD5 hashing function
func Md5Sum(str string) string {
hash := md5.New() // skipcq: GSC-G401
io.WriteString(hash, str)
return fmt.Sprintf("%x", hash.Sum(nil))
}
// Sha1Sum generates and returns a checksum using the SHA-1 hashing function
func Sha1Sum(str string) string {
hash := sha1.New() // skipcq: GSC-G401
io.WriteString(hash, str)
return fmt.Sprintf("%x", hash.Sum(nil))
}
// CloseHandle closes the given closer object only if it is non-nil
func CloseHandle(handle io.Closer) {
if handle != nil {
handle.Close()
}
}
// DeleteMatchingFiles deletes files in a folder (root) that match a given regular expression.
// Returns the number of files that were deleted, and any error encountered.
func DeleteMatchingFiles(root, match string) (filesDeleted int, err error) {
2022-09-08 15:45:29 -07:00
files, err := os.ReadDir(root)
if err != nil {
return 0, err
}
for _, f := range files {
match, _ := regexp.MatchString(match, f.Name())
if match {
os.Remove(filepath.Join(root, f.Name()))
filesDeleted++
}
}
return filesDeleted, err
}
// FindResource searches for a file in the given paths and returns the first one it finds
// or a blank string if none of the paths exist
func FindResource(paths ...string) string {
var err error
for _, filepath := range paths {
if _, err = os.Stat(filepath); err == nil {
return filepath
}
}
return ""
}
// GetFileParts returns the base filename, the filename sans-extension, and the extension
// sans-filename
func GetFileParts(filename string) (string, string, string) {
base := path.Base(filename)
var noExt string
var ext string
lastIndex := strings.LastIndex(base, ".")
if lastIndex > -1 {
noExt = base[:strings.LastIndex(base, ".")]
ext = path.Ext(base)[1:]
}
return base, noExt, ext
}
// GetFormattedFilesize returns a human readable filesize
func GetFormattedFilesize(size float64) string {
if size < 1000 {
return fmt.Sprintf("%dB", int(size))
} else if size <= 100000 {
return fmt.Sprintf("%fKB", size/1024)
} else if size <= 100000000 {
return fmt.Sprintf("%fMB", size/1024.0/1024.0)
}
return fmt.Sprintf("%0.2fGB", size/1024.0/1024.0/1024.0)
}
// GetRealIP checks the GC_TESTIP environment variable as well as HTTP_CF_CONNCTING_IP
// and X-Forwarded-For HTTP headers to get a potentially obfuscated IP address, before
// getting the request's reported remote address
func GetRealIP(request *http.Request) string {
if ip, ok := os.LookupEnv("GC_TESTIP"); ok {
return ip
}
if request.Header.Get("HTTP_CF_CONNECTING_IP") != "" {
return request.Header.Get("HTTP_CF_CONNECTING_IP")
}
if request.Header.Get("X-Forwarded-For") != "" {
return request.Header.Get("X-Forwarded-For")
}
remoteHost, _, err := net.SplitHostPort(request.RemoteAddr)
if err != nil {
return request.RemoteAddr
}
return remoteHost
}
// HackyStringToInt parses a string to an int, or 0 if error
func HackyStringToInt(text string) int {
value, _ := strconv.Atoi(text)
return value
}
// MarshalJSON creates a JSON string with the given data and returns the string and any errors
func MarshalJSON(data interface{}, indent bool) (string, error) {
var jsonBytes []byte
var err error
if indent {
jsonBytes, err = json.MarshalIndent(data, "", " ")
} else {
jsonBytes, err = json.Marshal(data)
}
if err != nil {
jsonBytes, _ = json.Marshal(map[string]string{"error": err.Error()})
}
return string(jsonBytes), err
}
// ParseIPRange takes a single IP address or an IP range of the form "networkIP/netmaskbits" and
// gives the starting IP and ending IP in the subnet
//
// More info: https://en.wikipedia.org/wiki/Subnet
func ParseIPRange(ipOrCIDR string) (string, string, error) {
var ipStart netip.Addr
if strings.ContainsRune(ipOrCIDR, '/') {
var ipEnd netip.Addr
// CIDR range
prefix, err := netip.ParsePrefix(ipOrCIDR)
if err != nil {
return "", "", err
}
ipStart = prefix.Addr()
ipEnd = prefix.Addr()
var tmp netip.Addr
for {
tmp = ipEnd.Next()
if !prefix.Contains(tmp) {
break
}
ipEnd = tmp
}
return ipStart.String(), ipEnd.String(), nil
}
// single IP
var err error
if ipStart, err = netip.ParseAddr(ipOrCIDR); err != nil {
return "", "", err
}
return ipStart.String(), ipStart.String(), nil
}
// GetIPRangeSubnet returns the smallest subnet that contains the start and end
// IP addresses, and any errors that occured
func GetIPRangeSubnet(start string, end string) (*net.IPNet, error) {
startIP := net.ParseIP(start)
endIP := net.ParseIP(end)
if startIP == nil {
return nil, fmt.Errorf("invalid IP address %s", start)
}
if endIP == nil {
return nil, fmt.Errorf("invalid IP address %s", end)
}
if len(startIP) != len(endIP) {
return nil, errors.New("ip addresses must both be IPv4 or IPv6")
}
if startIP.To4() != nil {
startIP = startIP.To4()
endIP = endIP.To4()
}
bits := 0
var ipn *net.IPNet
for b := range startIP {
if startIP[b] == endIP[b] {
bits += 8
continue
}
for i := 7; i >= 0; i-- {
if startIP[b]&(1<<i) == endIP[b]&(1<<i) {
bits++
continue
}
ipn = &net.IPNet{IP: startIP, Mask: net.CIDRMask(bits, len(startIP)*8)}
return ipn, nil
}
}
return &net.IPNet{IP: startIP, Mask: net.CIDRMask(bits, len(startIP)*8)}, nil
}
// ParseName takes a name string from a request object and returns the name and tripcode parts
2023-04-26 13:10:38 -07:00
func ParseName(name string) (string, string) {
var namePart string
var tripcodePart string
if !strings.Contains(name, "#") {
2023-04-26 13:10:38 -07:00
namePart = name
} else if strings.Index(name, "#") == 0 {
2023-04-26 13:10:38 -07:00
tripcodePart = tripcode.Tripcode(name[1:])
} else if strings.Index(name, "#") > 0 {
postNameArr := strings.SplitN(name, "#", 2)
2023-04-26 13:10:38 -07:00
namePart = postNameArr[0]
tripcodePart = tripcode.Tripcode(postNameArr[1])
}
2023-04-26 13:10:38 -07:00
return namePart, tripcodePart
}
// RandomString returns a randomly generated string of the given length
func RandomString(length int) string {
var str string
for i := 0; i < length; i++ {
2023-12-19 14:33:40 -08:00
num := rand.Intn(127) // skipcq: GSC-G404
if num < 32 {
num += 32
}
str += fmt.Sprintf("%c", num)
}
return str
}
func StripHTML(htmlIn string) string {
dom := x_html.NewTokenizer(strings.NewReader(htmlIn))
for tokenType := dom.Next(); tokenType != x_html.ErrorToken; {
if tokenType != x_html.TextToken {
tokenType = dom.Next()
continue
}
txtContent := strings.TrimSpace(x_html.UnescapeString(string(dom.Text())))
if len(txtContent) > 0 {
return x_html.EscapeString(txtContent)
}
tokenType = dom.Next()
}
return ""
}