liveserver

simple live web server for local developments
git clone https://noulin.net/git/liveserver.git
Log | Files | Refs | README | LICENSE

liveserver.c (11790B)


      1 #! /usr/bin/env sheepy
      2 
      3 /*
      4  * project:     miniweb
      5  * author:      Oscar Sanchez (oms1005@gmail.com)
      6  * HTTP Server
      7  * WORKS ON BROWSERS TOO!
      8  * Inspired by IBM's nweb
      9 */
     10 
     11 #include <stdio.h>
     12 #include <stdlib.h>
     13 #include <unistd.h>
     14 #include <errno.h>
     15 #include <string.h>
     16 #include <fcntl.h>
     17 #include <signal.h>
     18 #include <sys/types.h>
     19 #include <sys/socket.h>
     20 #include <netinet/in.h>
     21 #include <arpa/inet.h>
     22 #include "inoty.h"
     23 
     24 #include "libsheepyObject.h"
     25 #include "utilities.h"
     26 
     27 #define BUFSIZE 8092
     28 #define ERROR 42
     29 #define SORRY 43
     30 #define LOG   44
     31 
     32 i64 waitTime = 0;
     33 
     34 smallArrayt *dirList;
     35 
     36 const char injected[] = "<!-- Code injected by live-server -->\n\
     37 <script type=\"text/javascript\">\n\
     38 	// <![CDATA[  <-- For SVG support\n\
     39 	if ('WebSocket' in window) {\n\
     40 		(function() {\n\
     41 			function refreshCSS() {\n\
     42 				var sheets = [].slice.call(document.getElementsByTagName(\"link\"));\n\
     43 				var head = document.getElementsByTagName(\"head\")[0];\n\
     44 				for (var i = 0; i < sheets.length; ++i) {\n\
     45 					var elem = sheets[i];\n\
     46 					head.removeChild(elem);\n\
     47 					var rel = elem.rel;\n\
     48 					if (elem.href && typeof rel != \"string\" || rel.length == 0 || rel.toLowerCase() == \"stylesheet\") {\n\
     49 						var url = elem.href.replace(/(&|\\?)_cacheOverride=\\d+/, '');\n\
     50 						elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());\n\
     51 					}\n\
     52 					head.appendChild(elem);\n\
     53 				}\n\
     54 			}\n\
     55 			var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';\n\
     56 			var address = protocol + window.location.host + window.location.pathname + '/ws';\n\
     57 			var socket = new WebSocket(address);\n\
     58 			socket.onmessage = function(msg) {\n\
     59 				if (msg.data == 'reload') window.location.reload();\n\
     60 				else if (msg.data == 'refreshcss') refreshCSS();\n\
     61 			};\n\
     62 			console.log('Live reload enabled.');\n\
     63 		})();\n\
     64 	}\n\
     65 	// ]]>\n\
     66 </script>\n\
     67 ";
     68 
     69 struct {
     70 	char *ext;
     71 	char *filetype;
     72 } extensions [] = {
     73 	{"gif", "image/gif" },
     74 	{"jpg", "image/jpeg"},
     75 	{"jpeg","image/jpeg"},
     76 	{"png", "image/png" },
     77 	{"ico", "image/png" },
     78 	{"zip", "image/zip" },
     79 	{"gz",  "image/gz"  },
     80 	{"tar", "image/tar" },
     81 	{"htm", "text/html" },
     82 	{"html","text/html" },
     83 	{"php", "image/php" },
     84 	{"cgi", "text/cgi"  },
     85 	{"asp", "text/asp"  },
     86 	{"jsp", "image/jsp" },
     87 	{"xml", "text/xml"  },
     88 	{"js",  "application/javascript"   },
     89 	{"css", "text/css"  },
     90 	{"csv", "text/csv"  },
     91 	{"//ws", "websocket"},
     92 
     93 	{0,0} };
     94 
     95 #define WEBSOCKET_SERVER_RESPONSE	"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
     96 
     97 #define WEBSOCK_PACKAGE_NAME "s"
     98 #define WEBSOCK_PACKAGE_VERSION "0"
     99 
    100 void slog(int type, char *s1, char *s2, int num)
    101 {
    102 	int fd ;
    103 	char logbuffer[BUFSIZE*2];
    104 
    105 	switch (type) {
    106 	case ERROR: (void)sprintf(logbuffer,"ERROR: %s:%s Errno=%d exiting pid=%d",s1, s2, errno,getpid()); break;
    107 	case SORRY:
    108 		(void)sprintf(logbuffer, "<HTML><BODY><H1>Web Server Sorry: %s %s</H1></BODY></HTML>\r\n", s1, s2);
    109 		(void)write(num,logbuffer,strlen(logbuffer));
    110 		(void)sprintf(logbuffer,"SORRY: %s:%s",s1, s2);
    111 		break;
    112 	case LOG: (void)sprintf(logbuffer," INFO: %s:%s:%d",s1, s2,num); break;
    113 	}
    114 
    115 	if((fd = open("server.log", O_CREAT| O_WRONLY | O_APPEND,0644)) >= 0) {
    116 		(void)write(fd,logbuffer,strlen(logbuffer));
    117 		(void)write(fd,"\n",1);
    118 		(void)close(fd);
    119 	}
    120 	if(type == ERROR || type == SORRY) exit(3);
    121 }
    122 
    123 void web(int fd, int hit)
    124 {
    125 	int j, file_fd, buflen, len;
    126 	long i, r;
    127 	char * fstr;
    128 	static char buffer[BUFSIZE+1];
    129 
    130 	r =read(fd,buffer,BUFSIZE);
    131 	if(r == 0 || r == -1) {
    132 		slog(SORRY,"failed to read browser request","",fd);
    133 	}
    134 	if(r > 0 && r < BUFSIZE)
    135 		buffer[r]=0;
    136 	else buffer[0]=0;
    137 
    138 	smallStringt *reqS = allocG(buffer);
    139 	smallArrayt *req   = splitG(reqS, "\r\n");
    140 	char *clientKey    = NULL;
    141 	char *origin       = NULL;
    142 	char *protocol     = NULL; // protocol not parsed, firefox 52 doesnt send it
    143 	forEachSmallArray(req, S) {
    144 		castS(s,S);
    145 		if (startsWithG(s, "Sec-WebSocket-Key:")) {
    146 			clientKey = ssGet(s) + 19;
    147 		}
    148 		if(startsWithSG(s, "Origin:")) {
    149 			origin = ssGet(s) + 8;
    150 		}
    151 		finishG(s);
    152 	}
    153 
    154 	for(i=0;i<r;i++)
    155 		if(buffer[i] == '\r' || buffer[i] == '\n')
    156 			buffer[i]='*';
    157 	slog(LOG,"request",buffer,hit);
    158 
    159 	if( strncmp(buffer,"GET ",4) && strncmp(buffer,"get ",4) )
    160 		slog(SORRY,"Only simple GET operation supported",buffer,fd);
    161 
    162 	for(i=4;i<BUFSIZE;i++) {
    163 		if(buffer[i] == ' ') {
    164 			buffer[i] = 0;
    165 			break;
    166 		}
    167 	}
    168 
    169 	for(j=0;j<i-1;j++)
    170 		if(buffer[j] == '.' && buffer[j+1] == '.')
    171 			slog(SORRY,"Parent directory (..) path names not supported",buffer,fd);
    172 
    173 	if( !strncmp(&buffer[0],"GET /\0",6) || !strncmp(&buffer[0],"get /\0",6) )
    174 		(void)strcpy(buffer,"GET /index.html");
    175 
    176 	bool isHTML      = false;
    177 	bool isWebsocket = false;
    178 
    179 	buflen=strlen(buffer);
    180 	fstr = (char *)0;
    181 	for(i=0;extensions[i].ext != 0;i++) {
    182 		len = strlen(extensions[i].ext);
    183 		if( !strncmp(&buffer[buflen-len], extensions[i].ext, len)) {
    184 			fstr =extensions[i].filetype;
    185 			if (startsWithG(extensions[i].ext, "htm")) isHTML = true;
    186 			else if (eqS(extensions[i].ext, "//ws")) isWebsocket = true;
    187 			break;
    188 		}
    189 	}
    190 	if(fstr == 0) slog(SORRY,"file extension type not supported",buffer,fd);
    191 
    192 	if (isWebsocket) {
    193 		// WEBSOCKET HANDLING
    194 
    195 		slog(LOG,"websocket","detected",hit);
    196 		slog(LOG,"key",clientKey,hit);
    197 
    198 		SHA1Context shactx;
    199 		SHA1Reset(&shactx);
    200 
    201 		char buf[1024];
    202 		sprintf(buf, "%s%s", clientKey, WEBSOCKET_SERVER_RESPONSE);
    203 
    204 		SHA1Input(&shactx, (unsigned char *) buf, strlen(buf));
    205 		SHA1Result(&shactx);
    206 
    207 		char sha1buf[45];
    208 		unsigned char sha1mac[20];
    209 
    210 		sprintf(sha1buf, "%08x%08x%08x%08x%08x", shactx.Message_Digest[0],
    211 				shactx.Message_Digest[1], shactx.Message_Digest[2],
    212 				shactx.Message_Digest[3], shactx.Message_Digest[4]);
    213 
    214 		for (i16 n = 0; n < (strlen(sha1buf) / 2); n++) {
    215 			sscanf(sha1buf + (n * 2), "%02hhx", sha1mac + n);
    216 		}
    217 
    218 		char base64buf[256];
    219 		base64_encode(sha1mac, 20, base64buf, 256);
    220 		memset(buf, 0, 1024);
    221 		snprintf(buf, 1024, "HTTP/1.1 101 Switching Protocols\r\n"
    222 				"Server: %s/%s\r\n"
    223 				"Upgrade: websocket\r\n"
    224 				"%s%s%s"
    225 				"Connection: Upgrade\r\n"
    226 				"Sec-WebSocket-Accept: %s\r\n%s%s"
    227 				"Access-Control-Allow-Headers: content-type\r\n\r\n", WEBSOCK_PACKAGE_NAME,
    228 				WEBSOCK_PACKAGE_VERSION, origin ? "Access-Control-Allow-Origin: " : "", origin ? origin : "", origin ? "\r\n" : "", base64buf, protocol ? protocol : "", protocol ? "\r\n" : "");
    229 
    230 		(void)write(fd,buf,strlen(buf));
    231 
    232 		// INOTIFY SETUP
    233 
    234 		// wait for changes and reload page
    235 		enum {lyes, lno};
    236 		i8 foundRelevantUpdate = lno;
    237 		while (foundRelevantUpdate == lno) {
    238 			// creating the INOTIFY instance
    239 			int length;
    240 			char ibuffer[EVENT_BUF_LEN];
    241 			int ifd = inotiFy_init();
    242 			if (ifd == -1) {
    243 				perror("inotiFy_init error: ");
    244 			}
    245 
    246 			int wd; // watch descriptor
    247 
    248 			forEachSmallArray(dirList, E) {
    249 				castS(e, E);
    250 				wd = inotiFy_add_watch( ifd, ssGet(e), IN_CREATE | IN_DELETE | IN_MODIFY);
    251 				finishG(e);
    252 			}
    253 
    254 			// read to determine the event change happens on “.” directory. Actually this read blocks until the change event occurs
    255 			length = read( ifd, ibuffer, EVENT_BUF_LEN );;
    256 
    257 			if (length == -1) {
    258 				perror("read error: ");
    259 			}
    260 
    261 			u32 i = 0;
    262 			// actually read return the list of change events happens. Here, read the change event one by one and process it accordingly.
    263 			while (( i < length )) {
    264 				struct inotiFy_event *event = ( struct inotiFy_event * ) &ibuffer[ i ];
    265 
    266 				for(i=0;extensions[i].ext != 0;i++) {
    267 					if (endsWithG(event->name, extensions[i].ext)) {
    268 						if ((event->mask & IN_CREATE) || (event->mask & IN_DELETE) || (event->mask & IN_MODIFY)) {
    269 							foundRelevantUpdate = lyes;
    270 						}
    271 					}
    272 				}
    273 
    274 				i += EVENT_SIZE + event->len;
    275 			}
    276 
    277 
    278 			// removing the “/tmp” directory from the watch list.
    279 			inotiFy_rm_watch( ifd, wd );
    280 
    281 			// closing the INOTIFY instance
    282 			close(ifd);
    283 		}
    284 
    285 		if (waitTime > 0) {
    286 			// wait before reloading
    287 			sleep(waitTime);
    288 		}
    289 
    290 		// WEBSOCKET SEND RELOAD TO CLIENT
    291 
    292 		// send reload
    293 		u32 fin  = 1;
    294 		u32 op   = 1;
    295 		u32 plen = 6;
    296 		u32 blen = plen+2;
    297 
    298 		buf[0]   = (1 << 7) + fin; // 0x81
    299 		buf[1]   = plen;
    300 		/* uint16_t *b = (uint16_t*)buf; */
    301 		/* *b = (plen << 9) + (op << 4) + fin; */
    302 		strcpy(buf+2, "reload");
    303 
    304 		(void)write(fd,buf,blen);
    305 	} else {
    306 
    307 		// SERVE REGULAR HTTP REQUESTS
    308 
    309 		char *serveFile = &buffer[5];
    310 
    311 		if (isHTML) {
    312 			size_t endBodyAt = 0;
    313 			createAllocateSmallArray(f);
    314 			readFileG(f, &buffer[5]);
    315 			smallStringt *tmp;
    316 			enumerateSmallArray(f, S, n) {
    317 				castS(s, S);
    318 				tmp = dupG(s);
    319 				if (hasG(lowerG(tmp), "</body>")) {
    320 					endBodyAt = n;
    321 					terminateG(tmp);
    322 					finishG(s);
    323 					break;
    324 				}
    325 				terminateG(tmp);
    326 				finishG(s);
    327 			}
    328 			injectSG(f, endBodyAt, injected);
    329 			writeFileG(f, "a");
    330 			terminateG(f);
    331 			serveFile = "a";
    332 		}
    333 
    334 		if(( file_fd = open(serveFile,O_RDONLY)) == -1)
    335 			slog(SORRY, "failed to open file",serveFile,fd);
    336 
    337 		slog(LOG,"SEND",serveFile,hit);
    338 
    339 		(void)sprintf(buffer,"HTTP/1.0 200 OK\r\nContent-Type: %s\r\n\r\n", fstr);
    340 		(void)write(fd,buffer,strlen(buffer));
    341 
    342 		while ((r = read(file_fd, buffer, BUFSIZE)) > 0 ) {
    343 			(void)write(fd,buffer,r);
    344 		}
    345 	}
    346 
    347 	terminateManyG(reqS, req);
    348 
    349 #ifdef LINUX
    350 	sleep(1);
    351 #endif
    352 	exit(1);
    353 }
    354 
    355 
    356 int main(int argc, char **argv)
    357 {
    358 	int i, port, pid, listenfd, socketfd, hit;
    359 	size_t length;
    360 	static struct sockaddr_in cli_addr;
    361 	static struct sockaddr_in serv_addr;
    362 	char *portS;
    363 	char *dir = NULL;
    364 
    365 	// defaults
    366 	port  = 8080;
    367 	portS = "8080";
    368 
    369 	forEachS(argv, a) {
    370 		if (startsWithG(a, "-port=")) {
    371 			portS = a+6;
    372 			port  = parseInt(portS);
    373 		}
    374 		else if (startsWithG(a, "-path=")) {
    375 			dir = a+6;
    376 		}
    377 		else if (startsWithG(a, "-wait=")) {
    378 			waitTime = parseInt(a+6);
    379 		}
    380 		else if (startsWithG(a, "--help") or startsWithG(a, "-h") or startsWithG(a, "-?")) {
    381 			printf("usage: server [-port=80] [-path=server_directory] [-wait=1]\n"
    382 					"\twait unit is second.\n"
    383 					"\tExample: server -port=8080 -path=./ -wait=1\n\n"
    384 					"\tOnly Supports:");
    385 			for(i=0;extensions[i].ext != 0;i++)
    386 				(void)printf(" %s",extensions[i].ext);
    387 
    388 			(void)printf("\n\tNot Supported: directories / /etc /bin /lib /tmp /usr /dev /sbin \n"
    389 				    );
    390 			XSUCCESS
    391 		}
    392 	}
    393 
    394 	if (dir) {
    395 		if( !strncmp(dir,"/"   ,2 ) || !strncmp(dir,"/etc", 5 ) ||
    396 				!strncmp(dir,"/bin",5 ) || !strncmp(dir,"/lib", 5 ) ||
    397 				!strncmp(dir,"/tmp",5 ) || !strncmp(dir,"/usr", 5 ) ||
    398 				!strncmp(dir,"/dev",5 ) || !strncmp(dir,"/sbin",6) ){
    399 			(void)printf("ERROR: Bad top directory %s, see server -?\n",dir);
    400 			exit(3);
    401 		}
    402 		if(chdir(dir) == -1){
    403 			(void)printf("ERROR: Can't Change to directory %s\n",dir);
    404 			exit(4);
    405 		}
    406 	}
    407 
    408 	dirList = allocG(rtSmallArrayt);
    409 	execO("find . -type d", dirList, NULL);
    410 
    411 	printf(GRN "http://0.0.0.0:%s/" RST "\nlog: server.log", portS);
    412 	fflush(stdout);
    413 
    414 	if(fork() != 0)
    415 		return 0;
    416 	(void)signal(SIGCLD, SIG_IGN);
    417 	(void)signal(SIGHUP, SIG_IGN);
    418 	for(i=0;i<32;i++)
    419 		(void)close(i);
    420 	(void)setpgrp();
    421 
    422 	slog(LOG,"http server starting",portS,getpid());
    423 
    424 	if((listenfd = socket(AF_INET, SOCK_STREAM,0)) <0)
    425 		slog(ERROR, "system call","socket",0);
    426 
    427 	if(port < 0 || port >60000)
    428 		slog(ERROR,"Invalid port number try [1,60000]",portS,0);
    429 	serv_addr.sin_family = AF_INET;
    430 	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    431 	serv_addr.sin_port = htons(port);
    432 	if(bind(listenfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr)) <0)
    433 		slog(ERROR,"system call","bind",0);
    434 	if( listen(listenfd,64) <0)
    435 		slog(ERROR,"system call","listen",0);
    436 
    437 	for(hit=1; ;hit++) {
    438 		length = sizeof(cli_addr);
    439 		if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, (socklen_t *)&length)) < 0)
    440 			slog(ERROR,"system call","accept",0);
    441 
    442 		if((pid = fork()) < 0) {
    443 			slog(ERROR,"system call","fork",0);
    444 		}
    445 		else {
    446 			if(pid == 0) {
    447 				(void)close(listenfd);
    448 				web(socketfd,hit);
    449 			} else {
    450 				(void)close(socketfd);
    451 			}
    452 		}
    453 	}
    454 
    455 	terminateG(dirList);
    456 
    457 }