gs.c (5391B)
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 #include "util.h" 12 13 /* defines */ 14 /* TODO remove BUF_SIZEs */ 15 #define LBUF_SIZE 1024 16 #define BUF_SIZE 100000 17 #define URL_SIZE 2048 18 19 /* typedefs */ 20 typedef struct { 21 char *ptr; 22 size_t len; 23 } Str; 24 25 /* variables */ 26 char *argv0; 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 s->ptr = erealloc(s->ptr, nlen+1); 36 memcpy(s->ptr+s->len, ptr, size*nmemb); 37 s->ptr[nlen] = '\0'; 38 s->len = nlen; 39 40 return size*nmemb; 41 } 42 43 /* HTTP POST request with content, returning response */ 44 static Str 45 http_post(char *content) 46 { 47 char *resmsg, url[URL_SIZE]; 48 long code; 49 CURL *curl; 50 CURLcode res; 51 Str resstr; 52 53 resstr.len = 0; 54 resstr.ptr = emalloc(resstr.len+1); 55 resstr.ptr[0] = '\0'; 56 57 /* init cURL */ 58 curl_global_init(CURL_GLOBAL_ALL); 59 if (!(curl = curl_easy_init())) { 60 curl_global_cleanup(); 61 die(1, "%s: cURL: could not init", argv0); 62 } 63 64 /* set cURL options */ 65 if (gist) 66 snprintf(url, sizeof(url), "%s/%s", ghurl, gist); 67 else 68 strcpy(url, ghurl); 69 curl_easy_setopt(curl, CURLOPT_URL, url); 70 curl_easy_setopt(curl, CURLOPT_USERAGENT, "gs/"VERSION); 71 if (user) { 72 if (strchr(user, ':')) { 73 curl_easy_setopt(curl, CURLOPT_USERPWD, user); 74 } else { 75 curl_easy_setopt(curl, CURLOPT_USERNAME, user); 76 curl_easy_setopt(curl, CURLOPT_PASSWORD, getpass("GitHub password: ")); 77 } 78 } 79 if (gist) 80 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); 81 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, content); 82 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, str_write); 83 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resstr); 84 85 /* run cURL */ 86 if ((res = curl_easy_perform(curl)) != CURLE_OK) { 87 curl_global_cleanup(); 88 die(1, "%s: cURL: %s", argv0, curl_easy_strerror(res)); 89 } 90 91 /* response checking and cleanup */ 92 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); 93 curl_easy_cleanup(curl); 94 curl_global_cleanup(); 95 /* 200 returned when editing gist, 201 when creating */ 96 if (code != (gist ? 200 : 201)) { 97 json_scanf(resstr.ptr, resstr.len, "{message: %Q}", &resmsg); 98 die(-1, "%s: [%d] could not create Gist: %s", 99 argv0, code, resmsg); 100 free(resmsg); 101 exit(1); 102 } 103 104 return resstr; 105 } 106 107 /* read file fp into returned string */ 108 static char * 109 file_str(FILE *fp) 110 { 111 char lbuf[LBUF_SIZE]; /* buffer for each line */ 112 char *str = ecalloc(LBUF_SIZE, sizeof(char)); /* complete file */ 113 long flen = 1; /* file length */ 114 115 /* loop through each line in the file, append it to str */ 116 while (fgets(lbuf, LBUF_SIZE, fp)) { 117 flen += strlen(lbuf); 118 str = erealloc(str, flen); 119 strcat(str, lbuf); 120 } 121 122 return str; 123 } 124 125 /* turn list of file names into returned json string */ 126 static char * 127 files_js(char *files[], int filec) 128 { 129 char *fbuf; /* file contents */ 130 char *js = emalloc(BUF_SIZE*sizeof(char)); /* json string returned */ 131 FILE *fp = stdin; /* read from stdin by default if no file is given */ 132 struct json_out jout = JSON_OUT_BUF(js, BUF_SIZE); 133 134 json_printf(&jout, "{ public: %B,", pub); 135 if (!desc && !gist) /* when creating new gist if no description given make it empty */ 136 desc = ""; 137 if (desc) /* only add description as blank if gist is being created */ 138 json_printf(&jout, "description: %Q, ", desc); 139 json_printf(&jout, "files: {"); 140 141 if (del) /* delete file if given by setting it to NULL */ 142 json_printf(&jout, "%Q: %Q", basename(del), NULL); 143 144 /* add each file */ 145 for (int i = 0; !i || i < filec; i++) { 146 if (filec && !(fp = fopen(files[i], "r"))) 147 die(1, "%s: %s: could not load file", argv0, files[i]); 148 if (filec) /* set file name if given */ 149 fname = files[i]; 150 if (!fname && gist) /* don't need file if we are editing gist */ 151 break; 152 if (!fname) /* check for file name when using stdin */ 153 die(1, "%s: file name not given", argv0); 154 if (i || del) /* insert comma if this is another file */ 155 json_printf(&jout, ","); 156 fbuf = file_str(fp); 157 json_printf(&jout, "%Q: { content: %Q }", basename(fname), fbuf); 158 free(fbuf); 159 } 160 161 json_printf(&jout, "} }"); 162 163 return js; 164 } 165 166 static void 167 usage(const int eval) 168 { 169 die(eval, "usage: %s [-pPhv] [-e ID [-D FILE]] [-d DESCRIPTION] [-f FILENAME]\n" 170 " [-g URL] [-u USER[:PASSWORD] | -U] FILES ...", argv0); 171 } 172 173 int 174 main(int argc, char *argv[]) 175 { 176 char *js, *url; 177 Str resstr; 178 179 ARGBEGIN { 180 case 'e': 181 gist = EARGF(usage(1)); 182 break; 183 case 'd': 184 desc = EARGF(usage(1)); 185 break; 186 case 'D': 187 del = EARGF(usage(1)); 188 break; 189 case 'f': 190 fname = EARGF(usage(1)); 191 break; 192 case 'g': 193 ghurl = EARGF(usage(1)); 194 break; 195 case 'p': 196 pub = 0; 197 break; 198 case 'P': 199 pub = 1; 200 break; 201 case 'u': 202 user = EARGF(usage(1)); 203 break; 204 case 'U': 205 user = NULL; 206 break; 207 case 'h': 208 usage(0); 209 case 'v': 210 printf("%s v%s (c) 2017 Ed van Bruggen\n", argv0, VERSION); 211 return 0; 212 default: 213 usage(1); 214 } ARGEND; 215 216 if (gist && !user) 217 die(1, "%s: cannot edit Gists without user", argv0); 218 219 js = files_js(argv, argc); 220 resstr = http_post(js); 221 222 json_scanf(resstr.ptr, resstr.len, "{html_url: %Q}", &url); 223 puts(url); 224 225 free(js); 226 free(url); 227 free(resstr.ptr); 228 229 return 0; 230 }