💾 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

View Raw

More Information

⬅️ Previous capture (2023-01-29)

-=-=-=-=-=-=-

liveserver

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 }