💾 Archived View for gmi.noulin.net › gitRepositories › liveserver › file › liveserver.c.gmi captured on 2024-07-09 at 02:22:19. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
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 }