1 /*
2  * lws-minimal-http-client
3  *
4  * Written in 2010-2019 by Andy Green <andy@warmcat.com>
5  *
6  * This file is made available under the Creative Commons CC0 1.0
7  * Universal Public Domain Dedication.
8  *
9  * This demonstrates the a minimal http client using lws.
10  *
11  * It visits https://warmcat.com/ and receives the html page there.  You
12  * can dump the page data by changing the #if 0 below.
13  */
14 
15 #include <libwebsockets.h>
16 #include <string.h>
17 #include <signal.h>
18 
19 static int interrupted, bad = 1, status;
20 static struct lws *client_wsi;
21 
22 static int
callback_http(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)23 callback_http(struct lws *wsi, enum lws_callback_reasons reason,
24 	      void *user, void *in, size_t len)
25 {
26 	uint8_t buf[1280];
27 	union lws_tls_cert_info_results *ci =
28 		(union lws_tls_cert_info_results *)buf;
29 
30 	switch (reason) {
31 
32 	/* because we are protocols[0] ... */
33 	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
34 		lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
35 			 in ? (char *)in : "(null)");
36 		client_wsi = NULL;
37 		break;
38 
39 	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
40 		status = lws_http_client_http_response(wsi);
41 		lwsl_notice("lws_http_client_http_response %d\n", status);
42 
43 		if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_COMMON_NAME,
44 					    ci, sizeof(buf) - sizeof(*ci)))
45 			lwsl_notice(" Peer Cert CN        : %s\n", ci->ns.name);
46 
47 		if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_ISSUER_NAME,
48 					    ci, sizeof(ci->ns.name)))
49 			lwsl_notice(" Peer Cert issuer    : %s\n", ci->ns.name);
50 
51 		if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_FROM,
52 					    ci, 0))
53 			lwsl_notice(" Peer Cert Valid from: %s", ctime(&ci->time));
54 
55 		if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_VALIDITY_TO,
56 					    ci, 0))
57 			lwsl_notice(" Peer Cert Valid to  : %s", ctime(&ci->time));
58 		if (!lws_tls_peer_cert_info(wsi, LWS_TLS_CERT_INFO_USAGE,
59 					    ci, 0))
60 			lwsl_notice(" Peer Cert usage bits: 0x%x\n", ci->usage);
61 		if (!lws_tls_peer_cert_info(wsi,
62 					    LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY,
63 					    ci, sizeof(buf) - sizeof(*ci))) {
64 			lwsl_notice(" Peer Cert public key:\n");
65 			lwsl_hexdump_notice(ci->ns.name, ci->ns.len);
66 		}
67 		break;
68 
69 	/* chunks of chunked content, with header removed */
70 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
71 		lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
72 #if 0  /* enable to dump the html */
73 		{
74 			const char *p = in;
75 
76 			while (len--)
77 				if (*p < 0x7f)
78 					putchar(*p++);
79 				else
80 					putchar('.');
81 		}
82 #endif
83 		return 0; /* don't passthru */
84 
85 	/* uninterpreted http content */
86 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
87 		{
88 			char buffer[1024 + LWS_PRE];
89 			char *px = buffer + LWS_PRE;
90 			int lenx = sizeof(buffer) - LWS_PRE;
91 
92 			if (lws_http_client_read(wsi, &px, &lenx) < 0)
93 				return -1;
94 		}
95 		return 0; /* don't passthru */
96 
97 	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
98 		lwsl_user("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
99 		client_wsi = NULL;
100 		bad = status != 200;
101 		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
102 		break;
103 
104 	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
105 		client_wsi = NULL;
106 		bad = status != 200;
107 		lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */
108 		break;
109 
110 	default:
111 		break;
112 	}
113 
114 	return lws_callback_http_dummy(wsi, reason, user, in, len);
115 }
116 
117 static const struct lws_protocols protocols[] = {
118 	{
119 		"http",
120 		callback_http,
121 		0,
122 		0,
123 	},
124 	{ NULL, NULL, 0, 0 }
125 };
126 
127 static void
sigint_handler(int sig)128 sigint_handler(int sig)
129 {
130 	interrupted = 1;
131 }
132 
main(int argc,const char ** argv)133 int main(int argc, const char **argv)
134 {
135 	struct lws_context_creation_info info;
136 	struct lws_client_connect_info i;
137 	struct lws_context *context;
138 	const char *p;
139 	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE
140 		   /*
141 		    * For LLL_ verbosity above NOTICE to be built into lws,
142 		    * lws must have been configured and built with
143 		    * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE
144 		    *
145 		    * | LLL_INFO   | LLL_PARSER  | LLL_HEADER | LLL_EXT |
146 		    *   LLL_CLIENT | LLL_LATENCY | LLL_DEBUG
147 		    */ ;
148 
149 	signal(SIGINT, sigint_handler);
150 
151 	if ((p = lws_cmdline_option(argc, argv, "-d")))
152 		logs = atoi(p);
153 
154 	lws_set_log_level(logs, NULL);
155 	lwsl_user("LWS minimal http client [<-d <verbosity>] [-l] [--h1]\n");
156 
157 	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
158 	info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
159 	info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
160 	info.protocols = protocols;
161 	/*
162 	 * since we know this lws context is only ever going to be used with
163 	 * one client wsis / fds / sockets at a time, let lws know it doesn't
164 	 * have to use the default allocations for fd tables up to ulimit -n.
165 	 * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
166 	 * will use.
167 	 */
168 	info.fd_limit_per_thread = 1 + 1 + 1;
169 
170 #if defined(LWS_WITH_MBEDTLS)
171 	/*
172 	 * OpenSSL uses the system trust store.  mbedTLS has to be told which
173 	 * CA to trust explicitly.
174 	 */
175 	info.client_ssl_ca_filepath = "./warmcat.com.cer";
176 #endif
177 
178 	context = lws_create_context(&info);
179 	if (!context) {
180 		lwsl_err("lws init failed\n");
181 		return 1;
182 	}
183 
184 	memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
185 	i.context = context;
186 	i.ssl_connection = LCCSCF_USE_SSL;
187 
188 	if (lws_cmdline_option(argc, argv, "-l")) {
189 		i.port = 7681;
190 		i.address = "localhost";
191 		i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED;
192 	} else {
193 		i.port = 443;
194 		i.address = "warmcat.com";
195 	}
196 	i.path = "/";
197 	i.host = i.address;
198 	i.origin = i.address;
199 
200 	/* force h1 even if h2 available */
201 	if (lws_cmdline_option(argc, argv, "--h1"))
202 		i.alpn = "http/1.1";
203 
204 	i.method = "GET";
205 
206 	i.protocol = protocols[0].name;
207 	i.pwsi = &client_wsi;
208 	lws_client_connect_via_info(&i);
209 
210 	while (n >= 0 && client_wsi && !interrupted)
211 		n = lws_service(context, 0);
212 
213 	lws_context_destroy(context);
214 	lwsl_user("Completed: %s\n", bad ? "failed" : "OK");
215 
216 	return bad;
217 }
218