small weather widget for X11
at main 453 lines 9.5 kB view raw
1/* 2 * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17#include <err.h> 18#include <stdio.h> 19#include <stdlib.h> 20#include <string.h> 21#include <unistd.h> 22#include <sys/select.h> 23#include "http.h" 24 25extern char *__progname; 26 27struct url * 28url_parse(const char *str) 29{ 30 struct url *url = NULL; 31 char *scheme, *host, *path; 32 unsigned int port; 33 int ret, pos; 34 size_t len, schemelen, hostlen, pathlen; 35 36 len = strlen(str); 37 scheme = malloc(len + 1); 38 if (scheme == NULL) 39 err(1, "malloc"); 40 host = malloc(len + 1); 41 if (host == NULL) 42 err(1, "malloc"); 43 path = malloc(len + 1); 44 if (path == NULL) 45 err(1, "malloc"); 46 47 /* scheme://host:port/path */ 48 ret = sscanf(str, "%[^:]://%[^:]:%u%s%n", scheme, host, &port, path, 49 &pos); 50 if (ret == 4) { 51 if (pos > len) 52 errx(1, "url_parse sscanf overflow"); 53 goto consolidate; 54 } 55 56 /* scheme://host/path */ 57 ret = sscanf(str, "%[^:]://%[^/]%s%n", scheme, host, path, &pos); 58 if (ret == 3) { 59 if (pos > len) 60 errx(1, "url_parse sscanf overflow"); 61 if (strcmp(scheme, "http") == 0) 62 port = 80; 63 else if (strcmp(scheme, "https") == 0) 64 port = 443; 65 else 66 goto cleanup; 67 goto consolidate; 68 } 69 70 goto cleanup; 71 72consolidate: 73 schemelen = strlen(scheme); 74 hostlen = strlen(host); 75 pathlen = strlen(path); 76 77 /* 78 * Put everything in a single chunk of memory so the caller can just 79 * free(url) 80 */ 81 len = sizeof(struct url) + schemelen + 1 + hostlen + 1 + pathlen + 1; 82 url = malloc(len); 83 if (url == NULL) 84 err(1, "malloc"); 85 86 url->scheme = (char *)url + sizeof(struct url); 87 len = strlcpy(url->scheme, scheme, schemelen + 1); 88 89 url->host = url->scheme + len + 1; 90 len = strlcpy(url->host, host, hostlen + 1); 91 92 url->path = url->host + len + 1; 93 len = strlcpy(url->path, path, pathlen + 1); 94 95 url->port = port; 96 97cleanup: 98 free(scheme); 99 free(host); 100 free(path); 101 102 return url; 103} 104 105char * 106url_encode(unsigned char *str) 107{ 108 char *ret = NULL; 109 size_t len, n; 110 111encode: 112 for (n = 0, len = 0; str[n] != '\0'; n++) { 113 if ((str[n] >= 'A' && str[n] <= 'Z') || 114 (str[n] >= 'a' && str[n] <= 'z') || 115 (str[n] >= '0' && str[n] <= '9') || 116 (str[n] == '-' || str[n] == '_' || str[n] == '.' || 117 str[n] == '~')) { 118 if (ret) 119 ret[len] = str[n]; 120 len++; 121 } else { 122 if (ret) 123 snprintf(ret + len, 4, "%%%02X", str[n]); 124 len += 3; 125 } 126 } 127 128 if (ret) { 129 ret[len] = '\0'; 130 return ret; 131 } 132 133 ret = malloc(len + 1); 134 if (ret == NULL) 135 err(1, "malloc"); 136 len = 0; 137 goto encode; 138} 139 140struct http_request * 141http_get(const char *surl) 142{ 143 struct url *url; 144 struct http_request *req; 145 struct hostent *he; 146 struct sockaddr_in addr; 147 size_t len, tlen; 148 char ip_s[16]; 149#if TLS 150 struct tls_config *tls_config; 151 int tret; 152#endif 153 154 url = url_parse(surl); 155 if (url == NULL) 156 return NULL; 157 158 req = malloc(sizeof(struct http_request)); 159 if (req == NULL) 160 err(1, "malloc"); 161 memset(req, 0, sizeof(struct http_request)); 162 req->url = url; 163 164 if (strcmp(url->scheme, "https") == 0) { 165#if TLS 166 req->https = 1; 167#else 168 errx(1, "requested HTTPS URL but no TLS support: %s", surl); 169#endif 170 } 171 172 he = gethostbyname(req->url->host); 173 if (he == NULL) { 174 warnx("couldn't resolve host %s: %s", req->url->host, 175 hstrerror(h_errno)); 176 goto error; 177 } 178 179 req->socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 180 if (req->socket == -1) 181 err(1, "socket"); 182 183 memset(&addr, 0, sizeof(addr)); 184 addr.sin_family = AF_INET; 185 addr.sin_port = htons(url->port); 186 addr.sin_addr = *((struct in_addr *)he->h_addr); 187 188 inet_ntop(AF_INET, &addr.sin_addr, ip_s, sizeof(ip_s)); 189 190#if DEBUG 191 printf("connecting to %s (%s) %sto fetch %s\n", 192 req->url->host, ip_s, req->https ? "(with TLS) " : "", 193 req->url->path); 194#endif 195 196 if (connect(req->socket, (struct sockaddr *)&addr, 197 sizeof(addr)) == -1) { 198 warn("failed connecting to %s (%s) port %d", 199 req->url->host, ip_s, req->url->port); 200 goto error; 201 } 202 203#if TLS 204 if (req->https) { 205 tls_config = tls_config_new(); 206 if (tls_config == NULL) 207 errx(1, "tls_config allocation failed"); 208 if (tls_config_set_protocols(tls_config, 209 TLS_PROTOCOLS_ALL) != 0) 210 errx(1, "tls set protocols failed: %s", 211 tls_config_error(tls_config)); 212 if (tls_config_set_ciphers(tls_config, "legacy") != 0) 213 errx(1, "tls set ciphers failed: %s", 214 tls_config_error(tls_config)); 215 216 req->tls = tls_client(); 217 if (req->tls == NULL) 218 errx(1, "tls_client allocation failed"); 219 220 if (tls_configure(req->tls, tls_config) != 0) 221 errx(1, "tls_configure failed: %s", 222 tls_config_error(tls_config)); 223 224 if (tls_connect_socket(req->tls, req->socket, 225 req->url->host) != 0) { 226 warnx("TLS connect to %s failed: %s", req->url->host, 227 tls_error(req->tls)); 228 goto error; 229 } 230 231 do { 232 tret = tls_handshake(req->tls); 233 } while (tret == TLS_WANT_POLLIN || tret == TLS_WANT_POLLOUT); 234 235 if (tret != 0) { 236 warnx("TLS handshake to %s failed: %s", 237 req->url->host, tls_error(req->tls)); 238 goto error; 239 } 240 } 241#endif 242 243 tlen = 256 + strlen(req->url->host) + strlen(req->url->path); 244 req->message = malloc(tlen); 245 if (req->message == NULL) 246 err(1, "malloc"); 247 248 len = snprintf(req->message, tlen, 249 "GET %s HTTP/1.0\r\n" 250 "Host: %s\r\n" 251 "User-Agent: %s\r\n" 252 "Accept: */*\r\n" 253 "\r\n", 254 req->url->path, req->url->host, __progname); 255 if (len > tlen) 256 errx(1, "snprintf overflow"); 257 258#if DEBUG 259 printf(">>>[%zu] %s\n", len, req->message); 260#endif 261 262#if TLS 263 if (req->https) { 264 do { 265 tlen = tls_write(req->tls, req->message, len); 266 } while (tlen == TLS_WANT_POLLIN || tlen == TLS_WANT_POLLOUT); 267 } else 268#endif 269 { 270 tlen = write(req->socket, req->message, len); 271 } 272 if (tlen != len) 273 err(1, "short write"); 274 275 return req; 276 277error: 278 http_req_free(req); 279 return NULL; 280} 281 282ssize_t 283http_req_read(struct http_request *req, char *data, size_t len) 284{ 285 fd_set fds; 286 struct timeval timeout; 287 ssize_t ret; 288 289 if (!req || !req->socket) 290 return -1; 291 292 FD_ZERO(&fds); 293 FD_SET(req->socket, &fds); 294 timeout.tv_sec = 0; 295 timeout.tv_usec = 0; 296 297 switch (select(req->socket + 1, &fds, NULL, NULL, &timeout)) { 298 case -1: 299 err(1, "select"); 300 case 0: 301 return 0; 302 } 303 304#if TLS 305 if (req->https) { 306 do { 307 ret = tls_read(req->tls, data, len); 308 } while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT); 309 } else 310#endif 311 { 312 ret = read(req->socket, data, len); 313 } 314#if DEBUG 315 printf("<<<[%zu] %s\n", len, data); 316#endif 317 318 if (ret == -1) { 319#if TLS 320 if (req->https) { 321 tls_close(req->tls); 322 tls_free(req->tls); 323 req->tls = NULL; 324 req->https = 0; 325 } 326#endif 327 close(req->socket); 328 req->socket = 0; 329 return -1; 330 } 331 return ret; 332} 333 334int 335http_req_skip_header(struct http_request *req) 336{ 337 size_t len, n; 338 339 for (;;) { 340 if (req->chunk_len > 3) { 341 /* 342 * Leave last 3 bytes of previous read in case \r\n\r\n 343 * happens across reads. 344 */ 345 memmove(req->chunk, req->chunk + req->chunk_len - 3, 346 req->chunk_len - 3); 347 req->chunk_len = 3; 348 } 349 len = http_req_read(req, req->chunk + req->chunk_len, 350 sizeof(req->chunk) - req->chunk_len); 351 if (len < 0) 352 return 0; 353 if (len == 0) 354 continue; 355 req->chunk_len += len; 356 357 for (n = 3; n < req->chunk_len; n++) { 358 if (req->chunk[n - 3] != '\r' || 359 req->chunk[n - 2] != '\n' || 360 req->chunk[n - 1] != '\r' || 361 req->chunk[n] != '\n') 362 continue; 363 364 req->chunk_len -= n + 1; 365 memmove(req->chunk, req->chunk + n + 1, req->chunk_len); 366 req->chunk_off = 0; 367 return 1; 368 } 369 } 370 371 return 0; 372} 373 374char * 375http_req_chunk_peek(struct http_request *req) 376{ 377 if (req->chunk_len == 0 || (req->chunk_off + 1 > req->chunk_len)) { 378 req->chunk_len = http_req_read(req, req->chunk, 379 sizeof(req->chunk)); 380 if (req->chunk_len < 0) 381 return NULL; 382 req->chunk_off = 0; 383 } 384 385 if (req->chunk_len == 0 || (req->chunk_off + 1 > req->chunk_len)) 386 return NULL; 387 388 return req->chunk + req->chunk_off; 389} 390 391char * 392http_req_chunk_read(struct http_request *req) 393{ 394 char *chunk; 395 396 chunk = http_req_chunk_peek(req); 397 if (chunk == NULL) 398 return NULL; 399 400 req->chunk_off = req->chunk_len; 401 return chunk; 402} 403 404char 405http_req_byte_peek(struct http_request *req) 406{ 407 if (req->chunk_len == 0 || (req->chunk_off + 1 > req->chunk_len)) { 408 req->chunk_len = http_req_read(req, req->chunk, 409 sizeof(req->chunk)); 410 if (req->chunk_len < 0) 411 return 0; 412 req->chunk_off = 0; 413 } 414 415 if (req->chunk_len == 0 || (req->chunk_off + 1 > req->chunk_len)) 416 return 0; 417 418 return req->chunk[req->chunk_off]; 419} 420 421char 422http_req_byte_read(struct http_request *req) 423{ 424 char c; 425 426 c = http_req_byte_peek(req); 427 if (c == 0) 428 return 0; 429 430 req->chunk_off++; 431 return c; 432} 433 434void 435http_req_free(struct http_request *req) 436{ 437 if (req == NULL) 438 return; 439 440#if TLS 441 if (req->https && req->tls) { 442 tls_close(req->tls); 443 tls_free(req->tls); 444 req->tls = NULL; 445 } 446#endif 447 if (req->socket) 448 close(req->socket); 449 if (req->message != NULL) 450 free(req->message); 451 if (req->url) 452 free(req->url); 453}