/* yuno.c - Don Yang (uguu.org) ./yuno [port] 07/23/05 */ #include #include #include #include #include #include #include #ifdef _WIN32 #pragma warning(disable:4100) #pragma warning(disable:4127) #include #include #define LAST_ERROR() WSAGetLastError() #define CLOSE(_) closesocket(_) #define INIT_MUTEX(_) _ = CreateMutex(NULL, FALSE, NULL) #define UNINIT_MUTEX(_) CloseHandle(_) #define LOCK_MUTEX(_) WaitForSingleObject(_, INFINITE) #define UNLOCK_MUTEX(_) ReleaseMutex(_) typedef DWORD THREAD; #else #include #include #include #include #include #include #include #include #define SOCKET_ERROR (-1) #define INVALID_SOCKET (-1) #define LAST_ERROR() errno #define CLOSE(_) (void)close(_) #define INIT_MUTEX(_) (void)pthread_mutex_init(&(_), NULL) #define UNINIT_MUTEX(_) (void)pthread_mutex_destroy(&(_)) #define LOCK_MUTEX(_) (void)pthread_mutex_lock(&(_)) #define UNLOCK_MUTEX(_) (void)pthread_mutex_unlock(&(_)) typedef int SOCKET; typedef pthread_t THREAD; #endif /* Define this to print full HTTP requests to stdout #define LOG_REQUESTS */ #define CHARSET "Shift_JIS" #define MAX_REQUEST_SIZE 0x20000 #define ERRORMSG(_) \ do \ { \ if( RunServer != 0 ) printf(_ ": error %d\n", LAST_ERROR()); \ } while(0) typedef struct _node { char *msg; int size; struct _node *next; } Node; typedef struct { char req[MAX_REQUEST_SIZE + 1]; int size, header_size; SOCKET sock; } Request; static int RunServer = 1; static Node *Posts; #ifdef _WIN32 static HANDLE PostMutex; #else static pthread_mutex_t PostMutex; #endif static void CtrlC(int s); static /*@null@*/char *CookDataFixed(char *x); static /*@null@*/char *CookDataPlain(char *x); static /*@null@*/char *CookDataRaw(char *x); static void DecodeParams(char *request, /*@out@*/char **x, /*@out@*/char **f); static void DecodePercentEncoding(char *x); static void FreePosts(void); static void UnifyNewlines(char *s); static SOCKET InitServer(int port); static void ForkChild(SOCKET sock); #ifdef _WIN32 static DWORD WINAPI ReadRequest(SOCKET sock); #else static void *ReadRequest(SOCKET sock); #endif static void ProcessRequest(Request *request); static void SendReply(Request *request); int main(int argc, char **argv) { SOCKET sock; fd_set sset, rset; #ifdef _WIN32 WSADATA wsadata; if( WSAStartup(2, &wsadata) != 0 ) { printf("WSAStartup: error %d\n", WSAGetLastError()); return 1; } #endif INIT_MUTEX(PostMutex); Posts = NULL; RunServer = 1; signal(SIGINT, CtrlC); if( (sock = InitServer((argc > 1) ? atoi(argv[1]) : 80)) == SOCKET_ERROR ) return 1; FD_ZERO(&sset); FD_SET(sock, &sset); while( RunServer != 0 ) { memcpy(&rset, &sset, sizeof(fd_set)); if( select(FD_SETSIZE, &rset, NULL, NULL, NULL) == SOCKET_ERROR ) { ERRORMSG("select"); break; } if( FD_ISSET(sock, &rset) ) ForkChild(sock); } FreePosts(); UNINIT_MUTEX(PostMutex); #ifdef _WIN32 WSACleanup(); #endif return 0; } static void CtrlC(int s) { RunServer = 0; #ifdef _WIN32 /* Force winsock functions to fail so we can break out of select() */ WSACleanup(); #endif } static /*@null@*/char *CookDataFixed(char *x) { char *d, *r, *w; int size; time_t t; t = time(NULL); size = 0; for(r = x; *r != '\0'; r++) { if( *r == '&' ) size += 5; else if( *r == '<' ) size += 4; else if( *r == '>' ) size += 4; else size++; } if( (d = (char*)malloc(size + 64)) == NULL ) return NULL; strcpy(d, "\n
\n
");
   w = d + 11;
   for(r = x; *r != '\0'; r++)
   {
      if( *r == '&' )         { strcpy(w, "&"); w += 5; }
      else if( *r == '<' )    { strcpy(w, "<"); w += 4; }
      else if( *r == '>' )    { strcpy(w, ">"); w += 4; }
      else                    { *w++ = *r; }
   }

   sprintf(w, "

%.24s", ctime(&t)); return d; } static /*@null@*/char *CookDataPlain(char *x) { char *d, *r, *w; int size; time_t t; t = time(NULL); size = 0; for(r = x; *r != '\0'; r++) { if( *r == '&' ) size += 5; else if( *r == '<' ) size += 4; else if( *r == '>' ) size += 4; else if( *r == '\n' ) size += 4; else size++; } if( (d = (char*)malloc(size + 64)) == NULL ) return NULL; strcpy(d, "\n


\n"); w = d + 6; for(r = x; *r != '\0'; r++) { if( *r == '&' ) { strcpy(w, "&"); w += 5; } else if( *r == '<' ) { strcpy(w, "<"); w += 4; } else if( *r == '>' ) { strcpy(w, ">"); w += 4; } else if( *r == '\n' ) { strcpy(w, "
"); w += 4; } else { *w++ = *r; } } sprintf(w, "

%.24s", ctime(&t)); return d; } static /*@null@*/char *CookDataRaw(char *x) { char *d; time_t t; t = time(NULL); if( (d = (char*)malloc(strlen(x) + 64)) == NULL ) return NULL; sprintf(d, "\n


\n%s

%.24s", x, ctime(&t)); return d; } static void DecodeParams(char *request, /*@out@*/char **x, /*@out@*/char **f) { char *e; if( (*x = strstr(request, "x=")) != NULL ) *x += 2; if( (*f = strstr(request, "f=")) != NULL ) *f += 2; for(e = strchr(request, '&'); e != NULL; e = strchr(e + 1, '&')) *e = '\0'; if( *x != NULL ) DecodePercentEncoding(*x); } static void DecodePercentEncoding(char *x) { int d1, d2; char *r, *w; for(r = w = x; *r != '\0'; r++, w++) { if( *r == '+' ) { *w = ' '; } else if( *r == '%' ) { r++; if( *r == '\0' ) { *w = '\0'; return; } d1 = (*r >= '0' && *r <= '9') ? (int)*r - (int)'0' : (*r >= 'a' && *r <= 'f') ? (int)*r - (int)'a' + 10 : (*r >= 'A' && *r <= 'F') ? (int)*r - (int)'A' + 10 : 0; r++; if( *r == '\0' ) { *w = '\0'; return; } d2 = (*r >= '0' && *r <= '9') ? (int)*r - (int)'0' : (*r >= 'a' && *r <= 'f') ? (int)*r - (int)'a' + 10 : (*r >= 'A' && *r <= 'F') ? (int)*r - (int)'A' + 10 : 0; *w = (char)((d1 << 4) | d2); } else { *w = *r; } } *w = '\0'; } static void FreePosts(void) { Node *x, *next; LOCK_MUTEX(PostMutex); for(x = Posts; x != NULL; x = next) { next = x->next; free(x->msg); free(x); } Posts = NULL; UNLOCK_MUTEX(PostMutex); } static void UnifyNewlines(char *s) { char *r, *w; for(r = w = s; *r != '\0'; *w++ = *r++) { if( *r != '\r' ) continue; if( *(r + 1) == '\n' ) r++; else *r = '\n'; } *w = '\0'; } static SOCKET InitServer(int port) { struct sockaddr_in addr; SOCKET sock; if( (sock = socket(PF_INET, SOCK_STREAM, 0)) == SOCKET_ERROR ) { ERRORMSG("socket"); return INVALID_SOCKET; } addr.sin_family = AF_INET; addr.sin_port = htons((unsigned short)port); addr.sin_addr.s_addr = INADDR_ANY; if( bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) == SOCKET_ERROR ) { ERRORMSG("bind"); return INVALID_SOCKET; } if( listen(sock, 5) == SOCKET_ERROR ) { CLOSE(sock); ERRORMSG("listen"); return INVALID_SOCKET; } printf("listening on port %d\n", port); return sock; } static void ForkChild(SOCKET sock) { THREAD t; SOCKET a; struct sockaddr_in addr; #ifdef _WIN32 int size; #else socklen_t size; #endif size = sizeof(struct sockaddr_in); if( (a = accept(sock, (struct sockaddr*)&addr, &size)) == INVALID_SOCKET ) { ERRORMSG("accept"); return; } #ifdef _WIN32 CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ReadRequest, (LPVOID)a, 0, &t); #else pthread_create(&t, NULL, (void*(*)(void*))ReadRequest, (void*)a); #endif } #ifdef _WIN32 static DWORD WINAPI ReadRequest(SOCKET sock) #else static void *ReadRequest(SOCKET sock) #endif { Request request; char *w; int size; /* Read until two newlines are read */ request.sock = sock; request.size = 0; *(w = request.req) = '\0'; while( (size = recv(sock, w, MAX_REQUEST_SIZE - request.size, 0)) > 0 ) { w[size] = '\0'; request.size += size; w += size; if( strstr(request.req, "\r\n\r\n") != NULL || strstr(request.req, "\n\n") != NULL ) break; } #ifdef LOG_REQUESTS (void)puts("--------------------------------"); (void)fputs(request.req, stdout); #endif ProcessRequest(&request); SendReply(&request); return 0; } static void ProcessRequest(Request *request) { Node *node; char *req, *p, *x, *f; int size, rsize; req = request->req; if( req[0] != 'P' || req[1] != 'O' || req[2] != 'S' || req[3] != 'T' ) return; /* Find end of HTTP header */ if( (req = strstr(req, "\r\n\r\n")) != NULL ) req += 4; else if( (req = strstr(req, "\n\n")) != NULL ) req += 2; else return; request->header_size = (int)req - (int)(request->req); /* Check for post size */ *(req - 1) = '\0'; for(p = request->req; p != req; p++) *p = (char)tolower(*p); if( (p = strstr(request->req, "content-length:")) != NULL ) { size = atoi(p + strlen("content-length:")); size += request->header_size; if( size > MAX_REQUEST_SIZE ) size = MAX_REQUEST_SIZE; p = request->req + request->size; while( request->size < size ) { if( (rsize = recv(request->sock, p, size - request->size, 0)) <= 0 ) break; request->size += rsize; *(p + rsize) = '\0'; #ifdef LOG_REQUESTS (void)fputs(p, stdout); #endif p += rsize; } } #ifdef LOG_REQUESTS (void)putchar('\n'); #endif /* Decode post data */ DecodeParams(req, &x, &f); if( x == NULL || f == NULL ) return; /* Reload request, no need to process data */ if( memcmp(f, "reload", 6) == 0 ) return; /* Transform text */ UnifyNewlines(x); if( memcmp(f, "post", 4) == 0 ) { x = CookDataPlain(x); } else if( memcmp(f, "fixed", 5) == 0 ) { x = CookDataFixed(x); } else if( memcmp(f, "raw", 3) == 0 ) { x = CookDataRaw(x); } else { printf("Unrecognized request: %s\n", f); return; } if( x == NULL ) { (void)puts("Not enough memory, can not add more posts"); return; } /* Prepend post */ if( (node = (Node*)malloc(sizeof(Node))) == NULL ) { (void)puts("Not enough memory, can not add more posts"); free(x); return; } node->msg = x; node->size = strlen(x); LOCK_MUTEX(PostMutex); node->next = Posts; Posts = node; UNLOCK_MUTEX(PostMutex); } static void SendReply(Request *request) { /* HTML header */ static const char *header = "\n" "\n" "Yuno\n" "\n" "\n" "\n" "

\n" "


\n" "\n" "\n" "\n" "\n" "

\n"; static const char *footer = "\n"; Node *node; char *reply, *p; int size; /* Posts */ size = strlen(header) + strlen(footer); LOCK_MUTEX(PostMutex); for(node = Posts; node != NULL; node = node->next) size += node->size; if( (reply = (char*)malloc(size + 256)) == NULL ) { (void)puts("Out of memory"); } else { sprintf(reply, "HTTP/1.0 200 OK\r\n" "Content-Type: text/html\r\n" "Server: Yuno\r\n" "Connection: close\r\n" "Content-Length: %d\r\n\r\n" "%s", size, header); p = reply + strlen(reply); for(node = Posts; node != NULL; node = node->next) { strcpy(p, node->msg); p += node->size; } strcpy(p, footer); } UNLOCK_MUTEX(PostMutex); /* Send reply */ if( send(request->sock, reply, strlen(reply), 0) == SOCKET_ERROR ) ERRORMSG("send"); CLOSE(request->sock); free(reply); }