From 560d1ddd1b7a7fb55a6244d0b7f1ebf8f42780a0 Mon Sep 17 00:00:00 2001 From: Banana Date: Wed, 16 Mar 2022 14:34:45 +0100 Subject: [PATCH] selfpase go client ready --- client/c-client-linux/selfpaste.c | 13 +- client/go-client/README | 1 + client/go-client/go.mod | 2 + client/go-client/go.sum | 3 + client/go-client/selfpaste.go | 212 +++++++++++++++++++++++++++--- 5 files changed, 202 insertions(+), 29 deletions(-) diff --git a/client/c-client-linux/selfpaste.c b/client/c-client-linux/selfpaste.c index 25a1059..244564f 100644 --- a/client/c-client-linux/selfpaste.c +++ b/client/c-client-linux/selfpaste.c @@ -5,7 +5,7 @@ * COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 * along with this program. If not, see http://www.sun.com/cddl/cddl.html * - * 2019 - 2020 https://://www.bananas-playground.net/projekt/selfpaste + * 2019 - 2022 https://://www.bananas-playground.net/projekt/selfpaste */ /** @@ -40,14 +40,13 @@ static char args_doc[] = "file"; /* The options we understand. */ static struct argp_option options[] = { {"verbose",'v', 0, 0, "Produce verbose output" }, - {"quiet", 'q', 0, 0, "Don't produce any output" }, {"create-config-file", 'c', 0, 0, "Create default config file" }, { 0 } }; struct cmdArguments { char *args[1]; - int quiet, verbose, create_config_file; + int verbose, create_config_file; }; /* Parse a single option. */ @@ -56,9 +55,6 @@ parse_opt (int key, char *arg, struct argp_state *state) { struct cmdArguments *arguments = state->input; switch (key) { - case 'q': - arguments->quiet = 1; - break; case 'v': arguments->verbose = 1; break; @@ -173,7 +169,7 @@ int uploadCall(struct configOptions cfgo, struct cmdArguments arguments) { curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk); /* some servers don't like requests that are made without a user-agent */ /* field, so we provide one */ - curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "selfpaseCurlAgent/1.0"); + curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "selfpasteAgent/1.0"); /* add the POST data */ /* https://curl.haxx.se/libcurl/c/postit2.html */ @@ -241,7 +237,6 @@ int main(int argc, char *argv[]) { * command line argument parsing and default values */ struct cmdArguments arguments; - arguments.quiet = 0; arguments.verbose = 0; arguments.create_config_file = 0; @@ -250,11 +245,9 @@ int main(int argc, char *argv[]) { if(arguments.verbose) { printf ("File = %s\n" "Verbose = %s\n" - "Quiet = %s\n" "Create config file = %s\n", arguments.args[0], arguments.verbose ? "yes" : "no", - arguments.quiet ? "yes" : "no", arguments.create_config_file ? "yes" : "no" ); } diff --git a/client/go-client/README b/client/go-client/README index 6e356be..65ad9fc 100644 --- a/client/go-client/README +++ b/client/go-client/README @@ -6,3 +6,4 @@ This is a very simple, with limited experience written, go program. Use at own risk and feel free to improve. Howto build: +Nothing special, just go build -o selfpase \ No newline at end of file diff --git a/client/go-client/go.mod b/client/go-client/go.mod index b7aa292..31eec34 100644 --- a/client/go-client/go.mod +++ b/client/go-client/go.mod @@ -1,3 +1,5 @@ module selfpaste/go-client go 1.17 + +require gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/client/go-client/go.sum b/client/go-client/go.sum index e69de29..7534661 100644 --- a/client/go-client/go.sum +++ b/client/go-client/go.sum @@ -0,0 +1,3 @@ +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/client/go-client/selfpaste.go b/client/go-client/selfpaste.go index 46b69f2..910b753 100644 --- a/client/go-client/selfpaste.go +++ b/client/go-client/selfpaste.go @@ -6,6 +6,13 @@ import ( "log" "math/rand" "os" + "flag" + "gopkg.in/yaml.v2" + "net/http" + "io/ioutil" + "bytes" + "mime/multipart" + "encoding/json" ) // This program is free software: you can redistribute it and/or modify @@ -16,15 +23,73 @@ import ( // 2019 - 2022 https://://www.bananas-playground.net/projekt/selfpaste +/** + * !WARNING! + * This is a very simple, with limited experience written, go program. + * Use at own risk and feel free to improve + */ + const website = "https://www.bananas-playground.net/projekt/selfpaste" const version = "1.0" // used for non-existing default config const letters = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_" +// command line parameters +var optsVerbose bool +var optsCreateConfig bool +var optsDebug bool + +// config +var cfg Config +// config file struct +type Config struct { + Endpoint struct { + Host string `yaml:"host"` + Secret string `yaml:"secret"` + } `yaml:"endpoint"` +} + +// response json struct +type Response struct { + Message string `json:"message"` + Status int `json:"status"` +} + +/** + * Main + */ func main() { + + // parse commandline parameters + flag.BoolVar(&optsVerbose, "verbose", false, "Produce verbose output") + flag.BoolVar(&optsCreateConfig, "create-config-file", false, "Create default config file") + flag.BoolVar(&optsDebug, "debug", false, "Print debug infos") + flag.Parse() + if optsDebug { + fmt.Println("verbose:", optsVerbose) + fmt.Println("create-config-file:", optsCreateConfig) + fmt.Println("debug:", optsDebug) + } + + // load the config and populate Config loadConfig() + + // get the paylout + payload := getInput() + if optsDebug { log.Println(payload) } + + // do the upload and get the response + responseString := uploadCall(payload) + response := Response{} + json.Unmarshal([]byte(responseString), &response) + + // print the result and link to the pasty + fmt.Printf("Status: %d\n", response.Status) + fmt.Printf("Message: %s\n", response.Message) } + + /** * Check and display error with additional message */ @@ -34,6 +99,17 @@ func errorCheck(e error, msg string) { } } +/** + * just a random string + */ +func randStringBytes(n int) string { + b := make([]byte, n) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} + /** * load or even create a default config * $HOME/.selfpaste.yaml @@ -41,37 +117,135 @@ func errorCheck(e error, msg string) { func loadConfig() { homeDir, err := os.UserHomeDir() errorCheck(err, "No $HOME directory available?") - log.Printf("Your $HOME: %s \n", homeDir) + if optsVerbose { log.Printf("Your $HOME: %s \n", homeDir) } var configFile = homeDir + "/.selfpaste.yaml" if _, err := os.Stat(configFile); errors.Is(err, os.ErrNotExist) { - log.Printf("Config file not found. Creating default: %s \n", configFile) + log.Printf("Config file not found: %s \n", configFile) + + if optsCreateConfig { + log.Printf("Creating new default config file: %s \n", configFile) + + newConfig, err := os.Create(configFile) + errorCheck(err, "Can not create config file!") + defer newConfig.Close() + - newConfig, err := os.Create(configFile) - errorCheck(err, "Can not create config file!") - defer newConfig.Close() + _, err = fmt.Fprintf(newConfig, "# selfpaste go client config file.\n") + errorCheck(err, "Can not write to new config file") + fmt.Fprintf(newConfig, "# See %s for more details.\n", website) + fmt.Fprintf(newConfig, "# Version: %s\n", version) + fmt.Fprintf(newConfig, "endpoint:\n") + fmt.Fprintf(newConfig, " host: http://your-seflpaste-endpoi.nt\n") + fmt.Fprintf(newConfig, " secret: %s\n", randStringBytes(50)) + log.Fatalf("New default config file created: - %s - Edit and launch again!",configFile) + } + } + + existingConfigFile, err := os.Open(configFile) + errorCheck(err, "Can not open config file") + defer existingConfigFile.Close() + if optsVerbose { log.Printf("Reading config file: %s \n", configFile) } - _, err = fmt.Fprintf(newConfig, "# selfpaste go client config file.\n") - errorCheck(err, "Can not write to new config file") - fmt.Fprintf(newConfig, "# See %s for more details.\n", website) - fmt.Fprintf(newConfig, "# Version: %s\n", version) - fmt.Fprintf(newConfig, "endpoint:\n") - fmt.Fprintf(newConfig, " host: http://your-seflpaste-endpoi.nt\n") - fmt.Fprintf(newConfig, " secret: %s\n", randStringBytes(50)) + var decoder = yaml.NewDecoder(existingConfigFile) + err = decoder.Decode(&cfg) + errorCheck(err, "Can not decode config file") + + if cfg.Endpoint.Host == "" || cfg.Endpoint.Secret == "" { + log.Fatal("Empty config?") + } - log.Fatalf("New default config file created: - %s - Edit and launch again!",configFile) + if optsDebug { + log.Println(cfg.Endpoint.Host) + log.Println(cfg.Endpoint.Secret) } } /** - * just a random string + * Do a http POST call to the defined endpoint + * and upload the payload + * Return response body as string */ -func randStringBytes(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = letters[rand.Intn(len(letters))] +func uploadCall(payload string) string { + + if optsVerbose { log.Println("Starting to upload data") } + if optsDebug { log.Println(payload) } + if len(payload) == 0 { + log.Fatal("Nothing provided to upload") } - return string(b) + + // Buffer to store our request body as bytes + var requestBody bytes.Buffer + + // Create a multipart writer + multiPartWriter := multipart.NewWriter(&requestBody) + + // file field + fileWriter, err := multiPartWriter.CreateFormFile("pasty", "pastyfile") + errorCheck(err, "Can not create form file field") + fileWriter.Write([]byte(payload)) + + dlField, err := multiPartWriter.CreateFormField("dl") + errorCheck(err, "Can not create form dl field") + dlField.Write([]byte(cfg.Endpoint.Secret)) + + multiPartWriter.Close() + + req, err := http.NewRequest(http.MethodPost, cfg.Endpoint.Host, &requestBody) + errorCheck(err, "Can not create http request") + // We need to set the content type from the writer, it includes necessary boundary as well + req.Header.Set("Content-Type", multiPartWriter.FormDataContentType()) + req.Header.Set("User-Agent", "selfpasteAgent/1.0"); + + // Do the request + client := &http.Client{} + response, err := client.Do(req) + errorCheck(err, "POST request failed") + + responseBody, err := ioutil.ReadAll(response.Body) + errorCheck(err, "Can not read response body") + + if optsVerbose { log.Println("Request done") } + if optsDebug { + log.Printf("Response status code: %d\n",response.StatusCode) + log.Printf("Response headers: %#v\n", response.Header) + log.Println(string(responseBody)) + } + + return string(responseBody) } + +/** + * check if file is provided as commandline argument + * or piped into + * return the read data as string + */ +func getInput() string { + if optsVerbose { log.Println("Getting input") } + + var inputString string + + if filename := flag.Arg(0); filename != "" { + if optsVerbose { log.Println("Read from file argument") } + + bytes, err := os.ReadFile(filename) + errorCheck(err, "Error opening file") + inputString = string(bytes) + } else { + stat, _ := os.Stdin.Stat() + if (stat.Mode() & os.ModeCharDevice) == 0 { + if optsVerbose { log.Println("data is being piped") } + + bytes, _ := ioutil.ReadAll(os.Stdin) + inputString = string(bytes) + } + } + + if len(inputString) == 0 { + log.Fatal("Nothing provided to upload") + } + + return inputString +} \ No newline at end of file -- 2.39.5