1 /*
2  * ws protocol handler plugin for "client_loopback_test"
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  * The person who associated a work with this deed has dedicated
10  * the work to the public domain by waiving all of his or her rights
11  * to the work worldwide under copyright law, including all related
12  * and neighboring rights, to the extent allowed by law. You can copy,
13  * modify, distribute and perform the work, even for commercial purposes,
14  * all without asking permission.
15  *
16  * These test plugins are intended to be adapted for use in your code, which
17  * may be proprietary.  So unlike the library itself, they are licensed
18  * Public Domain.
19  */
20 
21 #if !defined(LWS_DLL)
22 #define LWS_DLL
23 #endif
24 #if !defined(LWS_INTERNAL)
25 #define LWS_INTERNAL
26 #endif
27 #include <libwebsockets.h>
28 #include <string.h>
29 
30 struct per_session_data__client_loopback_test {
31 	struct lws *wsi;
32 };
33 
34 /*
35  * This is a bit fiddly...
36  *
37  * 0) If you want the wss:// test to work, make sure the vhost is marked with
38  *    enable-client-ssl if using lwsws, or call lws_init_vhost_client_ssl() on
39  *    the vhost if you're doing it by hand.
40  *
41  * 1) enable the protocol on a vhost
42  *
43  *      "ws-protocols": [{
44  *     "client-loopback-test": {
45  *      "status": "ok"
46  *     },  ...
47  *
48  *     the vhost should listen on 80 (ws://) or 443 (wss://)
49  *
50  * 2) mount the http part of the test one level down on the same vhost, eg
51  *   {
52  *      "mountpoint": "/c",
53  *      "origin": "callback://client-loopback-test"
54  *   }
55  *
56  * 3) Use a browser to visit the mountpoint with a URI attached for looping
57  *    back, eg, if testing on localhost
58  *
59  *    http://localhost/c/ws://localhost
60  *    https://localhost/c/wss://localhost
61  *
62  * 4) The HTTP part of this test protocol will try to do the requested
63  *    ws client connection, to the same test protocol on the same
64  *    server.
65  */
66 
67 static int
callback_client_loopback_test(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)68 callback_client_loopback_test(struct lws *wsi, enum lws_callback_reasons reason,
69 			void *user, void *in, size_t len)
70 {
71 	struct lws_client_connect_info i;
72 	struct per_session_data__client_loopback_test *pss =
73 			(struct per_session_data__client_loopback_test *)user;
74 	const char *p = (const char *)in;
75 	char buf[100];
76 	int n;
77 
78 	switch (reason) {
79 
80 	/* HTTP part */
81 
82 	case LWS_CALLBACK_HTTP:
83 		if (len < 10)
84 			return -1;
85 
86 		p++;
87 		while (*p && *p != '/')
88 			p++;
89 		if (!*p) {
90 			lws_return_http_status(wsi, 400, "Arg needs to be in format ws://xxx or wss://xxx");
91 			return -1;
92 		}
93 		p++;
94 
95 		memset(&i, 0, sizeof(i));
96 		i.context = lws_get_context(wsi);
97 
98 		// stacked /// get resolved to /
99 
100 		if (strncmp(p, "ws:/", 4) == 0) {
101 			i.ssl_connection = 0;
102 			i.port = 80;
103 			p += 4;
104 		} else
105 			if (strncmp(p, "wss:/", 5) == 0) {
106 				i.port = 443;
107 				i.ssl_connection = 1;
108 				p += 5;
109 			} else {
110 				sprintf(buf, "Arg %s is not in format ws://xxx or wss://xxx\n", p);
111 				lws_return_http_status(wsi, 400, buf);
112 				return -1;
113 			}
114 
115 		i.address = p;
116 		i.path = "";
117 		i.host = p;
118 		i.origin = p;
119 		i.ietf_version_or_minus_one = -1;
120 		i.protocol = "client-loopback-test";
121 
122 		pss->wsi = lws_client_connect_via_info(&i);
123 		if (!pss->wsi)
124 			lws_return_http_status(wsi, 401, "client-loopback-test: connect failed\n");
125 		else {
126 			lwsl_notice("client connection to %s:%d with ssl: %d started\n",
127 				    i.address, i.port, i.ssl_connection);
128 			lws_return_http_status(wsi, 200, "OK");
129 		}
130 
131 		/* either way, close the triggering http link */
132 
133 		return -1;
134 
135 	case LWS_CALLBACK_CLOSED_HTTP:
136 		lwsl_notice("Http part closed\n");
137 		break;
138 
139 	/* server part */
140 
141 	case LWS_CALLBACK_ESTABLISHED:
142 		lwsl_notice("server part: LWS_CALLBACK_ESTABLISHED\n");
143 		strcpy(buf + LWS_PRE, "Made it");
144 		n = lws_write(wsi, (unsigned char *)buf + LWS_PRE,
145 			      7, LWS_WRITE_TEXT);
146 		if (n < 7)
147 			return -1;
148 		break;
149 
150 	/* client part */
151 
152 	case LWS_CALLBACK_CLIENT_ESTABLISHED:
153 		lwsl_notice("Client connection established\n");
154 		break;
155 
156 	case LWS_CALLBACK_CLIENT_RECEIVE:
157 		lws_strncpy(buf, in, sizeof(buf));
158 		lwsl_notice("Client connection received %ld from server '%s'\n",
159 			    (long)len, buf);
160 
161 		/* OK we are done with the client connection */
162 		return -1;
163 
164 	default:
165 		break;
166 	}
167 
168 	return 0;
169 }
170 
171 LWS_VISIBLE const struct lws_protocols client_loopback_test_protocols[] = {
172 	{
173 		"client-loopback-test",
174 		callback_client_loopback_test,
175 		sizeof(struct per_session_data__client_loopback_test),
176 		1024, /* rx buf size must be >= permessage-deflate rx size */
177 	},
178 };
179 
180 LWS_VISIBLE const lws_plugin_protocol_t client_loopback_test = {
181 	.hdr = {
182 		"client loopback test",
183 		"lws_protocol_plugin",
184 		LWS_BUILD_HASH,
185 		LWS_PLUGIN_API_MAGIC
186 	},
187 
188 	.protocols = client_loopback_test_protocols,
189 	.count_protocols = LWS_ARRAY_SIZE(client_loopback_test_protocols),
190 	.extensions = NULL,
191 	.count_extensions = 0,
192 };
193