selfpaste-win.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. /**
  2. * This program is free software: you can redistribute it and/or modify
  3. * it under the terms of the GNU General Public License as published by
  4. * the Free Software Foundation, either version 3 of the License, or
  5. * (at your option) any later version.
  6. *
  7. * This program is distributed in the hope that it will be useful,
  8. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. * GNU General Public License for more details.
  11. *
  12. * You should have received a copy of the GNU General Public License
  13. * along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.
  14. *
  15. * 2019 - 2023 https://://www.bananas-playground.net/projekt/selfpaste
  16. */
  17. /**
  18. * !WARNING!
  19. * This is a very simple, with limited experience written, windows C program.
  20. * Use at own risk and feel free to improve.
  21. *
  22. * for requirements and how to build it, read the README
  23. */
  24. #include <stdio.h>
  25. #include <stdlib.h>
  26. #include <string.h>
  27. #include <direct.h>
  28. #include <dirent.h>
  29. #include <errno.h>
  30. /* https://www.argtable.org */
  31. #include <argtable3.h>
  32. /* https://github.com/curl/curl-for-win curl+openssl */
  33. #include <curl/curl.h>
  34. /* https://github.com/DaveGamble/cJSON */
  35. #include <cJSON.h>
  36. /**
  37. * global arg_xxx structs
  38. * https://www.argtable.org/
  39. */
  40. struct arg_lit *verbose, *quiet, *help, *createConfigFile;
  41. struct arg_file *fileToPaste;
  42. struct arg_end *end;
  43. const char *program_version = "1.1";
  44. const char *program_bug_address = "https://://www.bananas-playground.net/projekt/selfpaste";
  45. struct cmdArguments {
  46. int quiet, verbose, create_config_file;
  47. char *file_to_paste;
  48. };
  49. /**
  50. * Simple random string generation
  51. */
  52. const char availableChars[] = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
  53. int intN(int n) { return rand() % n; }
  54. char *randomString(int len) {
  55. char *rstr = malloc((len + 1) * sizeof(char));
  56. int i;
  57. for (i = 0; i < len; i++) {
  58. rstr[i] = availableChars[intN(strlen(availableChars))];
  59. }
  60. rstr[len] = '\0';
  61. return rstr;
  62. }
  63. /**
  64. * struct to hold the config options loaded from config file
  65. * Extend if the options file changes.
  66. */
  67. struct configOptions {
  68. char *secret;
  69. char *endpoint;
  70. };
  71. /**
  72. * struct to hold the returned data from the http post call
  73. * done with curl
  74. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  75. */
  76. struct MemoryStruct {
  77. char *memory;
  78. size_t size;
  79. };
  80. /**
  81. * callback function from the curl call
  82. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  83. */
  84. static size_t
  85. WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
  86. struct MemoryStruct *mem = (struct MemoryStruct *)userp;
  87. size_t realsize = size * nmemb;
  88. char *ptr = realloc(mem->memory, mem->size + realsize + 1);
  89. if(ptr == NULL) {
  90. /* out of memory! */
  91. printf("not enough memory (realloc returned NULL)\n");
  92. return 0;
  93. }
  94. mem->memory = ptr;
  95. memcpy(&(mem->memory[mem->size]), contents, realsize);
  96. mem->size += realsize;
  97. mem->memory[mem->size] = 0;
  98. return realsize;
  99. }
  100. /**
  101. * make a post curl call to upload the given file
  102. * and receive the URL as a answer
  103. * see: https://curl.haxx.se/libcurl/c/getinmemory.html
  104. */
  105. int uploadCall(struct configOptions cfgo, struct cmdArguments arguments) {
  106. CURL *curl_handle;
  107. CURLcode res;
  108. struct MemoryStruct chunk;
  109. chunk.memory = malloc(1); /* will be grown as needed by the realloc above */
  110. chunk.size = 0; /* no data at this point */
  111. res = curl_global_init(CURL_GLOBAL_ALL);
  112. if(res != CURLE_OK) {
  113. printf("ERROR: curl_global_init() failed: %s\n", curl_easy_strerror(res));
  114. return 1;
  115. }
  116. /* init the curl session */
  117. curl_handle = curl_easy_init();
  118. /* specify URL to get */
  119. curl_easy_setopt(curl_handle, CURLOPT_URL, cfgo.endpoint);
  120. /* send all data to this function */
  121. curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
  122. /* we pass our 'chunk' struct to the callback function */
  123. curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
  124. /* some servers don't like requests that are made without a user-agent */
  125. /* field, so we provide one */
  126. curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "selfpaseCurlAgent/1.0");
  127. /* add the POST data */
  128. /* https://curl.haxx.se/libcurl/c/postit2.html */
  129. curl_mime *form = NULL;
  130. curl_mimepart *field = NULL;
  131. form = curl_mime_init(curl_handle);
  132. field = curl_mime_addpart(form);
  133. curl_mime_name(field, "pasty");
  134. curl_mime_filedata(field, arguments.file_to_paste);
  135. field = curl_mime_addpart(form);
  136. curl_mime_name(field, "dl");
  137. curl_mime_data(field, cfgo.secret, CURL_ZERO_TERMINATED);
  138. curl_easy_setopt(curl_handle, CURLOPT_MIMEPOST, form);
  139. /* execute it! */
  140. res = curl_easy_perform(curl_handle);
  141. /* check for errors */
  142. if(res != CURLE_OK || chunk.size < 1) {
  143. printf("ERROR: curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
  144. exit(1);
  145. }
  146. if (chunk.memory != NULL) {
  147. if(arguments.verbose) printf("%lu bytes retrieved\n", (unsigned long)chunk.size);
  148. if(arguments.verbose) printf("CURL returned:\n%s\n", chunk.memory);
  149. /* https://spacesciencesoftware.wordpress.com/2013/09/10/a-good-way-to-read-json-with-c/ */
  150. cJSON *json_root = cJSON_Parse(chunk.memory);
  151. if (json_root != NULL) {
  152. cJSON *json_result_status = cJSON_GetObjectItem(json_root, "status");
  153. if (json_result_status != NULL) {
  154. printf("Status: %s\n", json_result_status->valuestring);
  155. } else {
  156. printf("ERROR: Invalid payload returned. Missing 'status'\n%s\n", chunk.memory);
  157. }
  158. cJSON *json_result_message = cJSON_GetObjectItem(json_root, "message");
  159. if (json_result_message != NULL) {
  160. printf("Message: %s\n", json_result_message->valuestring);
  161. } else {
  162. printf("ERROR: Invalid payload returned. Missing 'message'\n%s\n", chunk.memory);
  163. }
  164. }
  165. }
  166. /* cleanup curl stuff */
  167. curl_easy_cleanup(curl_handle);
  168. curl_mime_free(form);
  169. free(chunk.memory);
  170. curl_global_cleanup();
  171. return 0;
  172. }
  173. /**
  174. * the main part starts here
  175. */
  176. int main(int argc, char *argv[]) {
  177. srand(time(NULL));
  178. /**
  179. * command line argument default values
  180. */
  181. struct cmdArguments arguments;
  182. arguments.quiet = 0;
  183. arguments.verbose = 0;
  184. arguments.create_config_file = 0;
  185. arguments.file_to_paste = "-";
  186. /**
  187. * https://www.argtable.org/
  188. */
  189. void *argtable[] = {
  190. help = arg_litn(NULL, "help", 0, 1, "Display this help and exit"),
  191. quiet = arg_litn("q", "quiet", 0, 1, "Don't produce any output"),
  192. verbose = arg_litn("v", "verbose", 0, 1, "Verbose output"),
  193. createConfigFile = arg_litn("c", "create-config-file", 0, 1, "Create default config file"),
  194. fileToPaste = arg_filen(NULL, NULL, "<file>", 0, 1, "File to paste"),
  195. end = arg_end(20),
  196. };
  197. /* argtable parsing */
  198. int nerrors;
  199. nerrors = arg_parse(argc,argv,argtable);
  200. /* special case: '--help' takes precedence over error reporting */
  201. if (help->count > 0) {
  202. printf("Usage: selfpaste.exe");
  203. arg_print_syntax(stdout, argtable, "\n");
  204. arg_print_glossary(stdout, argtable, " %-25s %s\n");
  205. arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
  206. return(1);
  207. }
  208. /* If the parser returned any errors then display them and exit */
  209. if (nerrors > 0) {
  210. /* Display the error details contained in the arg_end struct.*/
  211. arg_print_errors(stdout, end, "selfpaste.exe");
  212. printf("Try '%s --help' for more information.\n", "selfpaste.exe");
  213. arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
  214. return(1);
  215. }
  216. else {
  217. arguments.quiet = quiet->count;
  218. arguments.verbose = verbose->count;
  219. arguments.create_config_file = createConfigFile->count;
  220. arguments.file_to_paste = fileToPaste->filename[0];
  221. }
  222. if(arguments.verbose) {
  223. printf ("File = %s\n"
  224. "Verbose = %s\n"
  225. "Quiet = %s\n"
  226. "Create config file = %s\n",
  227. arguments.file_to_paste,
  228. arguments.verbose ? "yes" : "no",
  229. arguments.quiet ? "yes" : "no",
  230. arguments.create_config_file ? "yes" : "no"
  231. );
  232. }
  233. /**
  234. * Config file check.
  235. * Also create if not is available and command line option
  236. * to create it is set.
  237. */
  238. char* homedir = getenv("USERPROFILE");
  239. if(homedir == NULL) {
  240. printf("ERROR: USERPROFILE directory not found?\n");
  241. return(1);
  242. }
  243. if(arguments.verbose) printf("Homedir: '%s'\n", homedir);
  244. char selfpasteSaveDir[12] = "\\selfpaste";
  245. char storagePath[strlen(homedir) + strlen(selfpasteSaveDir)];
  246. strcpy(storagePath, homedir);
  247. strcat(storagePath, selfpasteSaveDir);
  248. DIR* checkStoragePathDir = opendir(storagePath);
  249. if(checkStoragePathDir) {
  250. if(arguments.verbose) printf("Storage directory exists: '%s'\n", storagePath);
  251. closedir(checkStoragePathDir);
  252. } else if (ENOENT == errno) {
  253. if(_mkdir(storagePath) == 0) {
  254. if(arguments.verbose) printf("Storage directory created: '%s'\n", storagePath);
  255. }
  256. else {
  257. printf("ERROR: Storage directory '%s' could not created\n", storagePath);
  258. return(1);
  259. }
  260. }
  261. else {
  262. printf("ERROR: Storage directory '%s' could not validated.\n", storagePath);
  263. return(1);
  264. }
  265. char configFileName[16] = "\\selfpaste.cfg";
  266. char configFilePath[strlen(storagePath) + strlen(configFileName)];
  267. strcpy(configFilePath, storagePath);
  268. strcat(configFilePath, configFileName);
  269. if(arguments.verbose) printf("Configfilepath: '%s'\n", configFilePath);
  270. if(access(configFilePath, F_OK) != -1) {
  271. if(arguments.verbose) printf("Using configfile: '%s'\n", configFilePath);
  272. if(arguments.create_config_file == 1) {
  273. printf("INFO: Re creating configfile by manually deleting it.\n");
  274. return(1);
  275. }
  276. } else {
  277. printf("ERROR: Configfile '%s' not found.\n",configFilePath);
  278. if(arguments.create_config_file == 1) {
  279. printf("Creating configfile: '%s'\n", configFilePath);
  280. FILE *fp = fopen(configFilePath, "w");
  281. if (fp) {
  282. fputs("# selfpaste config file.\n", fp);
  283. fprintf(fp, "# See %s for more details.\n", program_bug_address);
  284. fprintf(fp, "# Version: %s\n", program_version);
  285. fprintf(fp, "SELFPASTE_UPLOAD_SECRET=%s\n", randomString(50));
  286. fputs("ENDPOINT=http://you-seflpaste-endpoi.nt\n", fp);
  287. fclose(fp);
  288. printf("Config file '%s' created.\nPlease update your settings!\n", configFilePath);
  289. return(0);
  290. }
  291. else {
  292. printf("ERROR: Configfile '%s' could not be written.\n",configFilePath);
  293. }
  294. }
  295. return(1);
  296. }
  297. /**
  298. * Reading and parsing the config file.
  299. * populate configOptions struct
  300. *
  301. * https://rosettacode.org/wiki/Read_a_configuration_file#C
  302. * https://github.com/welljsjs/Config-Parser-C
  303. * https://hyperrealm.github.io/libconfig/
  304. * https://www.gnu.org/software/libc/manual/html_node/Finding-Tokens-in-a-String.html
  305. */
  306. struct configOptions configOptions;
  307. FILE* fp;
  308. if ((fp = fopen(configFilePath, "r")) == NULL) {
  309. printf("ERROR: Configfile '%s' could not be opened.\n",configFilePath);
  310. exit(1);
  311. }
  312. if(arguments.verbose) printf("Reading configfile: '%s'\n", configFilePath);
  313. char line[128];
  314. char *optKey,*optValue, *workwith;
  315. while (fgets(line, sizeof line, fp) != NULL ) {
  316. if(arguments.verbose) printf("- Line: %s", line);
  317. if (line[0] == '#') continue;
  318. /* important. strok modifies the string it works with */
  319. workwith = strdup(line);
  320. optKey = strtok(workwith, "=");
  321. if(arguments.verbose) printf("Option: %s\n", optKey);
  322. optValue = strtok(NULL, "\n\r");
  323. if(arguments.verbose) printf("Value: %s\n", optValue);
  324. if(strcmp("ENDPOINT",optKey) == 0) {
  325. configOptions.endpoint = optValue;
  326. }
  327. if(strcmp("SELFPASTE_UPLOAD_SECRET",optKey) == 0) {
  328. configOptions.secret = optValue;
  329. }
  330. }
  331. fclose(fp);
  332. if(arguments.verbose) {
  333. printf("Using\n- Secret: %s\n- Endpoint: %s\n- File: %s\n",
  334. configOptions.secret,configOptions.endpoint,
  335. arguments.file_to_paste);
  336. }
  337. /* do the upload */
  338. uploadCall(configOptions, arguments);
  339. return(0);
  340. }