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