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 }