gst

painless gist creator
git clone git://edryd.org/gst
Log | Files | Refs | LICENSE

gst.c (6133B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <curl/curl.h>
      3 #include <libgen.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 #include <unistd.h>
      8 
      9 #include "extern/arg.h"
     10 #include "extern/frozen.h"
     11 
     12 /* defines */
     13 #define LBUF_SIZE 1024
     14 #define BUF_SIZE  100000
     15 #define URL_SIZE  2048
     16 
     17 /* typedefs */
     18 typedef struct {
     19 	char *ptr;
     20 	size_t len;
     21 } Str;
     22 
     23 /* variables */
     24 char *argv0;
     25 
     26 static char *file_str(FILE *fp);
     27 
     28 #include "config.h"
     29 
     30 /* used by cURL to write its response to a Str */
     31 static size_t
     32 str_write(void *ptr, size_t size, size_t nmemb, Str *s)
     33 {
     34 	size_t nlen = s->len + size*nmemb;
     35 	if (!(s->ptr = realloc(s->ptr, nlen+1)))
     36 		perror("realloc: "), exit(1);
     37 	memcpy(s->ptr+s->len, ptr, size*nmemb);
     38 	s->ptr[nlen] = '\0';
     39 	s->len = nlen;
     40 
     41 	return size*nmemb;
     42 }
     43 
     44 /* HTTP POST request with content, returning response */
     45 static Str
     46 http_post(char *content)
     47 {
     48 	char *resmsg, url[URL_SIZE], *tokenstr;
     49 	long code;
     50 	CURL *curl;
     51 	CURLcode res;
     52 	Str resstr;
     53 	struct curl_slist *chunk = NULL;
     54 
     55 	resstr.len = 0;
     56 	if (!(resstr.ptr = malloc(resstr.len+1)))
     57 		perror("malloc: "), exit(1);
     58 	resstr.ptr[0] = '\0';
     59 
     60 	/* init cURL */
     61 	curl_global_init(CURL_GLOBAL_ALL);
     62 	if (!(curl = curl_easy_init())) {
     63 		curl_global_cleanup();
     64 		fprintf(stderr, "%s:  cURL: could not init", argv0);
     65 		exit(1);
     66 	}
     67 
     68 	/* set cURL options */
     69 	if (gist)
     70 		/* TODO if given URL split to get ID */
     71 		snprintf(url, sizeof(url), "%s/%s", ghurl, gist);
     72 	else
     73 		strcpy(url, ghurl);
     74 	curl_easy_setopt(curl, CURLOPT_URL, url);
     75 	curl_easy_setopt(curl, CURLOPT_USERAGENT, "gst/"VERSION);
     76 	if (gist)
     77 		curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
     78 	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, content);
     79 	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, str_write);
     80 	curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resstr);
     81 	if (tfile) {
     82 		FILE *f = fopen(tfile, "r");
     83 		if (!f) fprintf(stderr, "%s: %s: could not load file", argv0, tfile), exit(1);
     84 		token = file_str(f);
     85 		fclose(f);
     86 	}
     87 	if (token) {
     88 		if (!(tokenstr = malloc((23+strlen(token))*sizeof(char))))
     89 			perror("malloc: "), exit(1);
     90 		strcpy(tokenstr, "Authorization: token ");
     91 		strcat(tokenstr, token);
     92 		chunk = curl_slist_append(chunk, tokenstr);
     93 		curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
     94 		free(tokenstr);
     95 		if (tfile) free(token);
     96 	}
     97 
     98 	/* run cURL */
     99 	if ((res = curl_easy_perform(curl)) != CURLE_OK) {
    100 		curl_global_cleanup();
    101 		fprintf(stderr, "%s: cURL: %s", argv0, curl_easy_strerror(res));
    102 		exit(1);
    103 	}
    104 
    105 	/* response checking and cleanup */
    106 	curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
    107 	curl_easy_cleanup(curl);
    108 	curl_global_cleanup();
    109 	/* 200 returned when editing gist, 201 when creating */
    110 	if (code != (gist ? 200 : 201)) {
    111 		json_scanf(resstr.ptr, resstr.len, "{message: %Q}", &resmsg);
    112 		fprintf(stderr, "%s: [%d] could not create gist: %s", argv0, code, resmsg);
    113 		free(resmsg);
    114 		exit(1);
    115 	}
    116 
    117 	return resstr;
    118 }
    119 
    120 /* read file fp into returned string */
    121 static char *
    122 file_str(FILE *fp)
    123 {
    124 	char buf[LBUF_SIZE]; /* buffer for each chuck */
    125 	char *str = calloc(LBUF_SIZE, sizeof(char)); /* complete file */
    126 	size_t flen = 1; /* file length, start at 1 for null terminator */
    127 
    128 	if (!str) perror("calloc: "), exit(1);
    129 	/* loop through each LBUF_SIZE chunk in file, append it to str */
    130 	while (fgets(buf, LBUF_SIZE, fp)) {
    131 		flen += strlen(buf);
    132 		str = realloc(str, flen);
    133 		if (!str) perror("realloc: "), exit(1);
    134 		strncat(str, buf, LBUF_SIZE);
    135 	}
    136 
    137 	return str;
    138 }
    139 
    140 /* turn list of file names into returned json string */
    141 static char *
    142 files_js(char *files[], int filec)
    143 {
    144 	char *fbuf; /* file contents */
    145 	char *js = malloc(BUF_SIZE*sizeof(char)); /* json string returned */
    146 	FILE *fp = stdin; /* read from stdin by default if no file is given */
    147 	struct json_out jout = JSON_OUT_BUF(js, BUF_SIZE);
    148 
    149 	if (!js) perror("malloc: "), exit(1);
    150 	json_printf(&jout, "{ public: %B,", pub);
    151 	if (!desc && !gist) /* when creating new gist if no description given make it empty */
    152 		desc = "";
    153 	if (desc) /* only add description as blank if gist is being created */
    154 		json_printf(&jout, "description: %Q, ", desc);
    155 	json_printf(&jout, "files: {");
    156 
    157 	if (del) /* delete file if given by setting it to NULL */
    158 		json_printf(&jout, "%Q: %Q", basename(del), NULL);
    159 
    160 	/* add each file */
    161 	for (int i = 0; !i || i < filec; i++) {
    162 		if (filec && !(fp = fopen(files[i], "r")))
    163 			fprintf(stderr, "%s: %s: could not load file", argv0, files[i]), exit(1);
    164 		if (filec) /* set file name if given */
    165 			fname = files[i];
    166 		if (!fname && gist) /* don't need file if we are editing gist */
    167 			break;
    168 		if (!fname) /* check for file name when using stdin */
    169 			fprintf(stderr, "%s: file not given", argv0), exit(1);
    170 		if (i || del) /* insert comma if this is another file */
    171 			json_printf(&jout, ",");
    172 		fbuf = file_str(fp);
    173 		json_printf(&jout, "%Q: { content: %Q }", basename(fname), fbuf);
    174 		fclose(fp);
    175 		free(fbuf);
    176 	}
    177 
    178 	json_printf(&jout, "} }");
    179 
    180 	return js;
    181 }
    182 
    183 static void
    184 usage(const int eval)
    185 {
    186 	fprintf(stderr, "usage: %s [-pPhv] [-e ID [-D FILE]] [-d DESCRIPTION] [-f FILENAME]\n"
    187 	               "           [-g URL] [-U | -u TOKENFILE | -T TOKEN] FILES ...", argv0);
    188 	exit(eval);
    189 }
    190 
    191 int
    192 main(int argc, char *argv[])
    193 {
    194 	char *js, *url;
    195 	Str resstr;
    196 
    197 	ARGBEGIN {
    198 	case 'e':
    199 		gist = EARGF(usage(1));
    200 		break;
    201 	case 'd':
    202 		desc = EARGF(usage(1));
    203 		break;
    204 	case 'D':
    205 		del = EARGF(usage(1));
    206 		break;
    207 	case 'f':
    208 		fname = EARGF(usage(1));
    209 		break;
    210 	case 'g':
    211 		ghurl = EARGF(usage(1));
    212 		break;
    213 	case 'p':
    214 		pub = 0;
    215 		break;
    216 	case 'P':
    217 		pub = 1;
    218 		break;
    219 	case 'T':
    220 		token = EARGF(usage(1));
    221 		break;
    222 	case 'u':
    223 		tfile = EARGF(usage(1));
    224 		break;
    225 	case 'U':
    226 		tfile = NULL;
    227 		token = NULL;
    228 		break;
    229 	case 'h':
    230 		usage(0);
    231 	case 'v':
    232 		fprintf(stderr, "%s v%s (c) 2017-2021 Ed van Bruggen\n", argv0, VERSION);
    233 		return 0;
    234 	default:
    235 		usage(1);
    236 	} ARGEND;
    237 
    238 	if (gist && !token && !tfile)
    239 		fprintf(stderr, "%s: cannot edit gists without token", argv0), exit(1);
    240 
    241 	js = files_js(argv, argc);
    242 	resstr = http_post(js);
    243 
    244 	json_scanf(resstr.ptr, resstr.len, "{html_url: %Q}", &url);
    245 	puts(url);
    246 
    247 	free(js);
    248 	free(url);
    249 	free(resstr.ptr);
    250 
    251 	return 0;
    252 }