jcs
/wikipedia
/amendments
/6
http: A simple HTTP GET request wrapper
jcs made amendment 6 over 2 years ago
--- http.c Fri Aug 19 15:10:18 2022
+++ http.c Sun Aug 21 23:26:21 2022
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "dnr.h"
+#include "http.h"
+#include "util.h"
+#include "wikipedia.h"
+
+struct url *
+url_parse(const char *str)
+{
+ struct url *url = NULL;
+ char *buf, *scheme, *host, *path;
+ unsigned short port;
+ short ret, pos;
+ size_t len, schemelen, hostlen, pathlen;
+
+ len = strlen(str);
+ scheme = xmalloc(len + 1, "url_parse scheme");
+ host = xmalloc(len + 1, "url_parse host");
+ path = xmalloc(len + 1, "url_parse path");
+
+ /* scheme://host:port/path */
+ ret = sscanf(str, "%[^:]://%[^:]:%d%s%n", scheme, host, &port, path,
+ &pos);
+ if (ret == 4) {
+ if (pos > len)
+ panic("url_parse sscanf overflow");
+ goto consolidate;
+ }
+
+ /* scheme://host/path */
+ ret = sscanf(str, "%[^:]://%[^/]%s%n", scheme, host, path, &pos);
+ if (ret == 3) {
+ if (pos > len)
+ panic("url_parse sscanf overflow");
+ if (strcmp(scheme, "http") == 0)
+ port = 80;
+ else if (strcmp(scheme, "https") == 0)
+ port = 443;
+ else
+ goto cleanup;
+ goto consolidate;
+ }
+
+ goto cleanup;
+
+consolidate:
+ schemelen = strlen(scheme);
+ hostlen = strlen(host);
+ pathlen = strlen(path);
+
+ /*
+ * Put everything in a single chunk of memory so the caller can just
+ * free(url)
+ */
+ len = sizeof(struct url) + schemelen + 1 + hostlen + 1 + pathlen + 1;
+ url = xmalloc(len, "url");
+
+ url->scheme = (char *)url + sizeof(struct url);
+ len = strlcpy(url->scheme, scheme, schemelen + 1);
+
+ url->host = url->scheme + len + 1;
+ len = strlcpy(url->host, host, hostlen + 1);
+
+ url->path = url->host + len + 1;
+ len = strlcpy(url->path, path, pathlen + 1);
+
+ url->port = port;
+
+cleanup:
+ xfree(&scheme);
+ xfree(&host);
+ xfree(&path);
+
+ return url;
+}
+
+struct http_request *
+http_get(const char *surl)
+{
+ struct url *url;
+ struct http_request *req;
+ size_t len, alen;
+ short err;
+ char ip_s[16];
+ ip_addr local_ip;
+ tcp_port local_port;
+
+ url = url_parse(surl);
+ if (url == NULL)
+ return NULL;
+
+ if (_TCPInit() != 0)
+ panic("Failed initializing MacTCP");
+
+ req = xmalloczero(sizeof(struct http_request), "http_get");
+ req->url = url;
+ req->tcp_buf_size = (4 * 1500) + 1024;
+ req->tcp_buf = xmalloc(req->tcp_buf_size, "http_get buf");
+
+ err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf,
+ req->tcp_buf_size, nil, nil, nil, false);
+ if (err) {
+ warn("TCPCreate failed: %d", err);
+ goto error;
+ }
+
+ err = ResolveName(req->url->host, &req->host_ip);
+ if (err) {
+ warn("Couldn't resolve host %s (%d)", req->url->host, err);
+ goto error;
+ }
+
+ long2ip(req->host_ip, (char *)&ip_s);
+
+ err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, req->host_ip,
+ req->url->port, &local_ip, &local_port, nil, nil, false);
+ if (err) {
+ warn("Failed connecting to %s (%s) port %d: %d",
+ req->url->host, ip_s, req->url->port, err);
+ goto error;
+ }
+
+ err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
+ nil, nil, false);
+ if (err) {
+ warn("Failed TCPStatus on connection to %s (%s) port %d: %d",
+ req->url->host, ip_s, req->url->port, err);
+ goto error;
+ }
+
+ alen = 256 + strlen(req->url->host) + strlen(req->url->path);
+ req->message = xmalloc(alen, "http_get verb");
+ len = snprintf(req->message, alen,
+ "GET %s HTTP/1.0\r\n"
+ "Host: %s\r\n"
+ "User-Agent: %s\r\n"
+ "Accept: */*\r\n"
+ "\r\n", req->url->path, req->url->host, PROGRAM_NAME);
+ if (len > alen)
+ panic("snprintf overflow");
+
+ memset(&req->tcp_wds, 0, sizeof(req->tcp_wds));
+ req->tcp_wds[0].ptr = req->message;
+ req->tcp_wds[0].length = len;
+
+ err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, nil, nil,
+ false);
+ if (err) {
+ warn("TCPSend to %s (%s) failed: %d", req->url->host, ip_s, err);
+ goto error;
+ }
+
+ return req;
+
+error:
+ http_req_free(&req);
+ return NULL;
+}
+
+ssize_t
+http_req_read(struct http_request *req, char *data, size_t len)
+{
+ short err;
+ unsigned short rlen;
+
+ if (!req)
+ return -1;
+
+ err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb,
+ nil, nil, false);
+ if (err)
+ return -1;
+
+ if (req->tcp_status_pb.amtUnreadData == 0)
+ return 0;
+
+ rlen = MIN(req->tcp_status_pb.amtUnreadData, len);
+
+ err = _TCPRcv(&req->tcp_iopb, req->tcp_stream, data, &rlen, nil, nil,
+ false);
+ if (err)
+ return -1;
+
+ return rlen;
+}
+
+void
+http_req_free(void *reqptr)
+{
+ unsigned long *addr = (unsigned long *)reqptr;
+ void *ptr = (void *)*addr;
+ struct http_request *req = (struct http_request *)ptr;
+
+ if (req == NULL)
+ return;
+
+ _TCPRelease(&req->tcp_iopb, req->tcp_stream, nil, nil, false);
+
+ if (req->message != NULL)
+ xfree(&req->message);
+ xfree(&req->tcp_buf);
+ xfree(&req->url);
+ xfree(&req);
+}
--- http.h Fri Aug 19 14:08:56 2022
+++ http.h Fri Aug 19 14:08:56 2022
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __HTTP_H__
+#define __HTTP_H__
+
+#include "util.h"
+#include "tcp.h"
+
+struct url {
+ char *scheme;
+ char *host;
+ unsigned short port;
+ char *path;
+};
+
+struct http_request {
+ struct url *url;
+ ip_addr host_ip;
+
+ TCPiopb tcp_iopb;
+ StreamPtr tcp_stream;
+ unsigned char *tcp_buf;
+ size_t tcp_buf_size;
+ wdsEntry tcp_wds[2];
+ TCPStatusPB tcp_status_pb;
+
+ char *message;
+};
+
+struct url * url_parse(const char *str);
+
+struct http_request * http_get(const char *url);
+ssize_t http_req_read(struct http_request *req, char *data, size_t len);
+void http_req_free(void *reqptr);
+
+#endif