diff --git a/.gitignore b/.gitignore index a1338d6..ddfc70b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +code/ + # Binaries for programs and plugins *.exe *.dll @@ -12,3 +14,119 @@ # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +### Linux template +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* +### macOS template +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +### Windows template +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + diff --git a/.idea/go-fische.iml b/.idea/go-fische.iml new file mode 100644 index 0000000..c956989 --- /dev/null +++ b/.idea/go-fische.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml new file mode 100644 index 0000000..e39c1bd --- /dev/null +++ b/.idea/go.imports.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..64e6bba --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml new file mode 100644 index 0000000..500f3b7 --- /dev/null +++ b/.idea/watcherTasks.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..0e8428a --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 diff --git a/README.md b/docs/README.md similarity index 100% rename from README.md rename to docs/README.md diff --git a/_config.yml b/docs/_config.yml similarity index 100% rename from _config.yml rename to docs/_config.yml diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 0000000..d2739e4 Binary files /dev/null and b/docs/logo.png differ diff --git a/main.go b/main.go new file mode 100644 index 0000000..e660295 --- /dev/null +++ b/main.go @@ -0,0 +1,129 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "net" + "os" + "strconv" + "time" + + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const AppName = "Go-Fiche" + +const slugMap = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789" + +var ( + GitCommit, + GitBranch, + GitState, + BuildDate, + Version string +) + +func init() { + pflag.BoolP("help", "h", false, "Prints this help message") + pflag.StringP("output", "o", "./code", "Relative or absolute path to the directory where you want to store user-posted pastes.") + pflag.StringP("domain", "d", "localhost", "This will be used as a prefix for an output received by the client. Value will be prepended with http[s].") + pflag.IntP("port", "p", 9999, "Port in which the service should listen on.") + pflag.BoolP("https", "S", false, fmt.Sprintf("If set, %s returns url with https prefix instead of http.", AppName)) + pflag.StringP("username", "u", "", fmt.Sprintf("%s will try to switch to the requested user on startup if any is provided.", AppName)) + pflag.IntP("buffer", "B", 32768, "This parameter defines size of the buffer used for getting data from the user. Maximum size (in bytes) of all input files is defined by this value.") + pflag.StringP("log", "l", "", "Log file. This file has to be user-writable.") + pflag.Parse() + viper.BindPFlags(pflag.CommandLine) + + if viper.GetBool("help") { + fmt.Printf("%s!\n Version %s, Built: %s [%s:%s-%s)\n\n", AppName, Version, BuildDate, GitBranch, GitCommit, GitState) + pflag.Usage() + os.Exit(2) + } + + if viper.GetBool("https") { + viper.Set("uriprefix", "https") + } else { + viper.Set("uriprefix", "http") + } +} + +func main() { + log.Printf("Starting %s on %s...", AppName, time.Now().Format(time.RFC822Z)) + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", viper.Get("port"))) + if err != nil { + // handle error + log.Fatalf("Could not bind to port: %d!", viper.Get("port")) + os.Exit(-1) + } + log.Printf("Server started listening on port: %d.", viper.Get("port")) + for { + conn, err := listener.Accept() + if err != nil { + log.Printf("Error on accepting connection!") + continue + } + go handleConnection(conn) + } + time.Sleep(5) + os.Exit(0) +} + +func generateSlug(seed int64) string { + stringSeed := fmt.Sprintf("%d", seed) + evenLength := (len(stringSeed) / 2) * 2 + digitHold := int64(0) + out := "" + for i := 0; i < evenLength; i += 2 { + digitHold, _ = strconv.ParseInt(stringSeed[i:i+2], 10, 8) + digitHold = digitHold % int64(len(slugMap)) + out += slugMap[digitHold : digitHold+1] + } + return out +} + +func handleConnection(conn net.Conn) { + remoteHost, err := net.LookupCNAME(conn.RemoteAddr().String()) + if err != nil { + remoteHost = conn.RemoteAddr().String() + } + defer func() { + log.Printf("Closing connection to: %s (%s).\n", remoteHost, conn.RemoteAddr()) + conn.Close() + }() + log.Printf("Incoming connection from: %s (%s).\n", remoteHost, conn.RemoteAddr()) + conn.SetReadDeadline(time.Now().Add(time.Second * 5)) + + slug := generateSlug(time.Now().UnixNano()) + slugFullpath := fmt.Sprintf("%s/%s", viper.GetString("output"), slug) + log.Printf("Writing file to %s", slugFullpath) + + file, err := os.Create(slugFullpath) + if err != nil { + log.Fatalf("Unable to create slug file: %s", slugFullpath) + return + } + + scanner := bufio.NewScanner(conn) + for scanner.Scan() { + file.WriteString(scanner.Text()) + file.Write([]byte("\n")) + conn.SetReadDeadline(time.Now().Add(time.Second * 5)) + } + + stat, _ := file.Stat() + if stat.Size() <= 0 { + file.Close() + log.Printf("0 bytes received, aborting...") + os.Remove(slugFullpath) + return + } else { + file.Close() + } + + log.Printf("Wrote %d bytes to %s", stat.Size(), slugFullpath) + + conn.Write([]byte(fmt.Sprintf("%s://%s/%s\r\n", viper.GetString("uriprefix"), viper.GetString("domain"), slug))) +}