//----------------------------------------------------------------------------- // Parse the start-line // from RFC2616 / 5.1 Request-Line (start-line): // Request-Line = Method SP Request-URI SP HTTP-Version CRLF (SP=Space) // // Determine Reqest-Method, URL, HTTP-Version and Split Parameters // Split URL into path, filename, fileext .. UrlData[] //----------------------------------------------------------------------------- bool CWebserverRequest::ParseStartLine(std::string start_line) { std::string method,url,http,tmp; log_level_printf(8,"<ParseStartLine>: line: %s\n", start_line.c_str() ); if(ySplitString(start_line," ",method,tmp)) { if(ySplitStringLast(tmp," ",url,Connection->httprotocol)) { analyzeURL(url); UrlData["httprotocol"] = Connection->httprotocol; // determine http Method if(method.compare("POST") == 0) Connection->Method = M_POST; else if(method.compare("GET") == 0) Connection->Method = M_GET; else if(method.compare("PUT") == 0) Connection->Method = M_PUT; else if(method.compare("HEAD") == 0) Connection->Method = M_HEAD; else if(method.compare("PUT") == 0) Connection->Method = M_PUT; else if(method.compare("DELETE") == 0) Connection->Method = M_DELETE; else if(method.compare("TRACE") == 0) Connection->Method = M_TRACE; else { log_level_printf(1,"Unknown Method or invalid request\n"); Connection->Response.SendError(HTTP_NOT_IMPLEMENTED); log_level_printf(3,"Request: '%s'\n",rawbuffer.c_str()); return false; } log_level_printf(3,"Request: FullURL: %s\n",UrlData["fullurl"].c_str()); return true; } } return false; }
//----------------------------------------------------------------------------- // HOOK: Response Send Handler // If an other Hook has set HookVarList["CacheCategory"] then the Conntent // in hh->yresult should be cached into a file. // Remeber: url, filename, mimetype, category, createdate //----------------------------------------------------------------------------- THandleStatus CmodCache::Hook_SendResponse(CyhookHandler *hh) { hh->status = HANDLED_NONE; std::string url = hh->UrlData["fullurl"]; log_level_printf(4,"mod_cache hook start url:%s\n",url.c_str()); std::string category = hh->HookVarList["CacheCategory"]; if(!(hh->HookVarList["CacheCategory"]).empty()) // Category set = cache it { AddToCache(hh, url, hh->yresult, hh->HookVarList["CacheMimeType"], category); // create cache file and add to cache list hh->ContentLength = (hh->yresult).length(); hh->SendFile(CacheList[url].filename); // Send as file hh->ResponseMimeType = CacheList[url].mime_type; // remember mime } else if(hh->UrlData["path"] == "/y/") // /y/ commands { hh->status = HANDLED_READY; if(hh->UrlData["filename"] == "cache-info") yshowCacheInfo(hh); else if(hh->UrlData["filename"] == "cache-clear") yCacheClear(hh); else hh->status = HANDLED_CONTINUE; // y-calls can be implemented anywhere } log_level_printf(4,"mod_cache hook end status:%d\n",(int)hh->status); return hh->status; }
//============================================================================= // y Parsing and sending .yhtm Files (Main ENTRY) //============================================================================= void CyParser::ParseAndSendFile(CyhookHandler *hh) { bool ydebug = false; std::string yresult, ycmd; log_level_printf(3, "yParser.ParseAndSendFile: File: %s\n", (hh->UrlData["filename"]).c_str()); hh->SetHeader(HTTP_OK, "text/html; charset=UTF-8"); if (hh->Method == M_HEAD) return; if (hh->ParamList["debug"] != "") // switch debug on ydebug = true; if (hh->ParamList["execute"] != "") // execute done first! { ycmd = hh->ParamList["execute"]; ycmd = YPARSER_ESCAPE_START + ycmd + YPARSER_ESCAPE_END; log_level_printf(3, "<yParser.ParseAndSendFile>: Execute!: %s\n", ycmd.c_str()); yresult = cgi_cmd_parsing(hh, ycmd, ydebug); // parsing engine } // parsing given file yresult += cgi_file_parsing(hh, hh->UrlData["path"]+hh->UrlData["filename"], ydebug); if (yresult.length() <= 0) hh->SetError(HTTP_NOT_IMPLEMENTED, HANDLED_NOT_IMPLEMENTED); else { hh->addResult(yresult, HANDLED_READY); if (!ycgi_vars["cancache"].empty()) { hh->HookVarList["CacheCategory"] = ycgi_vars["cancache"]; hh->HookVarList["CacheMimeType"] = hh->ResponseMimeType; hh->status = HANDLED_CONTINUE; } } }
//------------------------------------------------------------------------- // Get text block named <blockname> from file <filename> // The textblock starts with "start-block~<blockname>" and ends with // "end-block~<blockname>" //------------------------------------------------------------------------- std::string CyParser::YWeb_cgi_include_block(std::string filename, std::string blockname, std::string ydefault) { std::string ytmp, yfile, yresult; struct stat attrib; yresult = ydefault; stat(filename.c_str(), &attrib); pthread_mutex_lock(&yParser_mutex); if ((attrib.st_mtime == yCached_blocks_attrib.st_mtime) && (filename == yCached_blocks_filename)) { log_level_printf(6, "include-block: (%s) from cache\n", blockname.c_str()); yfile = yCached_blocks_content; } else { bool found = false; for (unsigned int i = 0; i < HTML_DIR_COUNT && !found; i++) { std::string ifilename = HTML_DIRS[i] + "/" + filename; std::fstream fin(ifilename.c_str(), std::fstream::in); if (fin.good()) { found = true; while (!fin.eof()) // read whole file into yfile { getline(fin, ytmp); yfile += ytmp + "\n"; } fin.close(); } } yCached_blocks_content = yfile; yCached_blocks_attrib = attrib; yCached_blocks_filename = filename; log_level_printf(6, "include-block: (%s) from file\n", blockname.c_str()); } pthread_mutex_unlock(&yParser_mutex); if (yfile.length() != 0) { std::string t = "start-block~" + blockname; std::string::size_type start, end; if ((start = yfile.find(t)) != std::string::npos) { if ((end = yfile.find("end-block~" + blockname, start + t.length())) != std::string::npos) { yresult = yfile.substr(start + t.length(), end - (start + t.length())); log_level_printf(7, "include-block: (%s) yresult:(%s)\n", blockname.c_str(), yresult.c_str()); } else aprintf( "include-blocks: Block END not found:%s Blockname:%s\n", filename.c_str(), blockname.c_str()); } else aprintf("include-blocks: Block START not found:%s Blockname:%s\n", filename.c_str(), blockname.c_str()); } else aprintf("include-blocks: file not found:%s Blockname:%s\n", filename.c_str(), blockname.c_str()); return yresult; }
//------------------------------------------------------------------------- // Webserver-Thread for each connection //------------------------------------------------------------------------- void *WebThread(void *args) { TWebserverConnectionArgs *newConn = (TWebserverConnectionArgs *) args; if (!newConn) { dperror("WebThread called without arguments!\n"); return NULL; } bool is_threaded = newConn->is_treaded; if (is_threaded) log_level_printf(1, "++ Thread 0x06%X gestartet\n", (int) pthread_self()); // (1) create & init Connection CWebserver *ws = newConn->WebserverBackref; if (!ws) { dperror("WebThread CWebserver error!\n"); return NULL; } CWebserverConnection *con = new CWebserverConnection(ws); if (!con) { dperror("WebThread CWebserverConnection error!\n"); return NULL; } con->Request.UrlData["clientaddr"] = newConn->ySock->get_client_ip(); // TODO:here? con->sock = newConn->ySock; // give socket reference newConn->ySock->handling = true; // dont handle this socket now be webserver main loop // (2) handle the connection con->HandleConnection(); // (3) end connection handling #ifdef Y_CONFIG_FEATURE_KEEP_ALIVE if(!con->keep_alive) log_level_printf(2,"FD SHOULD CLOSE sock:%d!!!\n",con->sock->get_socket()); else ws->addSocketToMasterSet(con->sock->get_socket()); // add to master set #endif if (!con->keep_alive) con->sock->isValid = false; con->sock->handling = false; // socket can be handled by webserver main loop (select) again #ifndef Y_CONFIG_FEATURE_KEEP_ALIVE delete newConn->ySock; newConn->ySock = NULL; #endif // (4) end thread delete con; int thread_number = newConn->thread_number; delete newConn; if (is_threaded) { log_level_printf(1, "-- Thread 0x06%X beendet\n", (int) pthread_self()); ws->clear_Thread_List_Number(thread_number); pthread_exit( NULL); } return NULL; }
//============================================================================= // Parsing Request //============================================================================= //----------------------------------------------------------------------------- // Main Request Parsing // RFC2616 // generic-message = start-line // *(message-header CRLF) // CRLF // [ message-body ] // start-line = Request-Line | Status-Line //----------------------------------------------------------------------------- bool CWebserverRequest::HandleRequest(void) { std::string start_line = ""; // read first line do { start_line = Connection->sock->ReceiveLine(); if (!Connection->sock->isValid) return false; if (start_line == "") // Socket empty { log_level_printf(1, "HandleRequest: End of line not found\n"); Connection->Response.SendError(HTTP_INTERNAL_SERVER_ERROR); Connection->RequestCanceled = true; return false; } } while (start_line == "\r\n"); // ignore empty lines at begin on start-line start_line = trim(start_line); log_level_printf(1, "Request: %s\n", start_line.c_str()); UrlData["startline"] = start_line; if (!ParseStartLine(start_line)) return false; if (Connection->Method == M_GET || Connection->Method == M_HEAD) { std::string tmp_line; //read header (speed up: read rest of request in blockmode) tmp_line = Connection->sock->ReceiveBlock(); if (!Connection->sock->isValid) { Connection->Response.SendError(HTTP_INTERNAL_SERVER_ERROR); return false; } if (tmp_line == "") { Connection->Response.SendError(HTTP_INTERNAL_SERVER_ERROR); return false; } ParseHeader(tmp_line); } // Other Methods if (Connection->Method == M_DELETE || Connection->Method == M_PUT || Connection->Method == M_TRACE) { //todo: implement aprintf("HTTP Method not implemented :%d\n", Connection->Method); Connection->Response.SendError(HTTP_NOT_IMPLEMENTED); return false; } // handle POST (read header & body) if (Connection->Method == M_POST) { Connection->Response.Write("HTTP/1.1 100 Continue\r\n\r\n"); // POST Requests requires CONTINUE in HTTP/1.1 return HandlePost(); } // if you are here, something went wrong return true; }
//----------------------------------------------------------------------------- // BASIC Send File over Socket for FILE* // fd is an opened FILE-Descriptor //----------------------------------------------------------------------------- int CySocket::SendFile(int filed) { if (!isValid) return false; #ifdef Y_CONFIG_HAVE_SENDFILE // does not work with SSL !!! off_t start = 0; off_t end = lseek(filed,0,SEEK_END); int written = 0; if((written = ::sendfile(sock,filed,&start,end)) == -1) { perror("sendfile failed\n"); return false; } else BytesSend += written; #else char sbuf[1024]; unsigned int r = 0; while ((r = read(filed, sbuf, 1024)) > 0) { if (Send(sbuf, r) < 0) { perror("sendfile failed\n"); return false; } } #endif // Y_CONFIG_HAVE_SENDFILE log_level_printf(9, "<Sock:SendFile>: Bytes:%ld\n", BytesSend); return true; }
//----------------------------------------------------------------------------- // Look for Sockets to close //----------------------------------------------------------------------------- void CWebserver::CloseConnectionSocketsByTimeout() { CySocket *connectionSock = NULL; for (int j = 0; j < HTTPD_MAX_CONNECTIONS; j++) if (SocketList[j] != NULL // here is a socket && !SocketList[j]->handling) // it is not handled { connectionSock = SocketList[j]; SOCKET thisSocket = connectionSock->get_socket(); bool shouldClose = true; if (!connectionSock->isValid) // If not valid -> close ; // close else if (connectionSock->tv_start_waiting.tv_sec != 0 || SocketList[j]->tv_start_waiting.tv_usec != 0) { // calculate keep-alive timeout struct timeval tv_now; struct timezone tz_now; gettimeofday(&tv_now, &tz_now); int64_t tdiff = ((tv_now.tv_sec - connectionSock->tv_start_waiting.tv_sec) * 1000000 + (tv_now.tv_usec - connectionSock->tv_start_waiting.tv_usec)); if (tdiff < HTTPD_KEEPALIVE_TIMEOUT || tdiff < 0) shouldClose = false; } if (shouldClose) { log_level_printf(2, "FD: close con Timeout fd:%d\n", thisSocket); SL_CloseSocketBySlot(j); } } }
//============================================================================= // SocketList Handler //============================================================================= //----------------------------------------------------------------------------- // Accept new Connection //----------------------------------------------------------------------------- int CWebserver::AcceptNewConnectionSocket() { int slot = -1; CySocket *connectionSock = NULL; if (!(connectionSock = listenSocket.accept())) // Blocking wait { dperror("Socket accept error. Continue.\n"); delete connectionSock; return -1; } #ifdef Y_CONFIG_USE_OPEN_SSL if(Cyhttpd::ConfigList["SSL"]=="true") connectionSock->initAsSSL(); // make it a SSL-socket #endif log_level_printf(2, "FD: new con fd:%d on port:%d\n", connectionSock->get_socket(), connectionSock->get_accept_port()); // Add Socket to List slot = SL_GetFreeSlot(); if (slot < 0) { connectionSock->close(); aprintf("No free Slot in SocketList found. Open:%d\n", open_connections); } else { SocketList[slot] = connectionSock; // put it to list fcntl(connectionSock->get_socket(), F_SETFD, O_NONBLOCK); // set non-blocking open_connections++; // count open connectins int newfd = connectionSock->get_socket(); if (newfd > fdmax) // keep track of the maximum fd fdmax = newfd; } return slot; }
//----------------------------------------------------------------------------- // Read Webserver Configurationfile for languages //----------------------------------------------------------------------------- void Cyhttpd::ReadLanguage(void) { // Init Class vars CLanguage *lang = CLanguage::getInstance(); log_level_printf(3, "ReadLanguage:%s\n", ConfigList["Language.selected"].c_str()); lang->setLanguage(ConfigList["Language.selected"]); }
//----------------------------------------------------------------------------- // parse parameter string // parameter = attribute "=" value // attribute = token // value = token | quoted-string // // If parameter attribute is multiple times given, the values are stored like this: // <attribute>=<value1>,<value2>,..,<value n> //----------------------------------------------------------------------------- bool CWebserverRequest::ParseParams(std::string param_string) { bool ende = false; std::string param, name="", value, number; while(!ende) { if(!ySplitStringExact(param_string,"&",param,param_string)) ende = true; if(ySplitStringExact(param,"=",name,value)) { name = decodeString(name); value = trim(decodeString(value)); if(ParameterList[name].empty()) ParameterList[name] = value; else { ParameterList[name] += ","; ParameterList[name] += value; } } else name = trim(decodeString(name)); number = string_printf("%d", ParameterList.size()+1); log_level_printf(7,"ParseParams: name: %s value: %s\n",name.c_str(), value.c_str()); ParameterList[number] = name; } return true; }
//----------------------------------------------------------------------------- // Hook Dispatcher for Server based Hooks // Execute every Hook in HookList until State change != HANDLED_NONE and // != HANDLED_CONTINUE //----------------------------------------------------------------------------- THandleStatus CyhookHandler::Hooks_ReadConfig(CConfigFile *Config, CStringList &ConfigList) { log_level_printf(4, "ReadConfig Hook-List Start\n"); THandleStatus _status = HANDLED_NONE; THookList::iterator i = HookList.begin(); for (; i != HookList.end(); i++) { // log_level_printf(4,"ReadConfig Hook-List (%s) Start\n", ((*i)->getHookName()).c_str()); // response Hook _status = (*i)->Hook_ReadConfig(Config, ConfigList); log_level_printf(4, "ReadConfig Hook-List (%s) Status (%d)\n", ((*i)->getHookName()).c_str(), _status); if ((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE)) break; } log_level_printf(4, "ReadConfig Hook-List End\n"); return _status; }
//------------------------------------------------------------------------- // Main // Handle the Request, Handle (Send) Response), End the Connection //------------------------------------------------------------------------- void CWebserverConnection::HandleConnection() { gettimeofday(&tv_connection_start, &tz_connection_start); // get the request if (Request.HandleRequest()) { // determine time from Connection creation until now gettimeofday(&tv_connection_Response_start, &tz_connection_Response_start); enlapsed_request = ((tv_connection_Response_start.tv_sec - tv_connection_start.tv_sec) * 1000000 + (tv_connection_Response_start.tv_usec - tv_connection_start.tv_usec)); // Keep-Alive checking #ifdef Y_CONFIG_FEATURE_KEEP_ALIVE if(string_tolower(Request.HeaderList["Connection"]) == "close" || (httprotocol != "HTTP/1.1" && string_tolower(Request.HeaderList["Connection"]) != "keep-alive") || !Webserver->CheckKeepAliveAllowedByIP(sock->get_client_ip())) keep_alive = false; #else keep_alive = false; #endif // Send a response Response.SendResponse(); // determine time for SendResponse gettimeofday(&tv_connection_Response_end, &tz_connection_Response_end); enlapsed_response = ((tv_connection_Response_end.tv_sec - tv_connection_Response_start.tv_sec) * 1000000 + (tv_connection_Response_end.tv_usec - tv_connection_Response_start.tv_usec)); // print production times log_level_printf(1, "enlapsed time request:%ld response:%ld url:%s\n", enlapsed_request, enlapsed_response, (Request.UrlData["fullurl"]).c_str()); } else { RequestCanceled = true; keep_alive = false; // close this connection socket // dperror("Error while parsing request\n"); log_level_printf(1, "request canceled: %s\n", strerror(errno)); } EndConnection(); }
//----------------------------------------------------------------------------- // Hook Dispatcher for EndConnection //----------------------------------------------------------------------------- THandleStatus CyhookHandler::Hooks_EndConnection() { log_level_printf(4, "EndConnection Hook-List Start\n"); THandleStatus _status = HANDLED_NONE; THookList::iterator i = HookList.begin(); for (; i != HookList.end(); i++) { log_level_printf(4, "EndConnection Hook-List (%s) Start\n", ((*i)->getHookName()).c_str()); // response Hook _status = (*i)->Hook_EndConnection(this); log_level_printf(4, "EndConnection Hook-List (%s) End. Status (%d)\n", ((*i)->getHookName()).c_str(), _status); if ((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE)) break; } log_level_printf(4, "EndConnection Hook-List End\n"); status = _status; return _status; }
//----------------------------------------------------------------------------- // main parsing (nested and recursive) //----------------------------------------------------------------------------- std::string CyParser::cgi_cmd_parsing(CyhookHandler *hh, std::string html_template, bool ydebug) { std::string::size_type start, end; unsigned int esc_len = strlen(YPARSER_ESCAPE_START); bool is_cmd; std::string ycmd, yresult; do // replace all {=<cmd>=} nested and recursive { is_cmd = false; if ((end = html_template.find(YPARSER_ESCAPE_END)) != std::string::npos) // 1. find first y-end { if (ydebug) hh->printf("[ycgi debug]: END at:%d following:%s<br/>\n", end, (html_template.substr(end, 10)).c_str()); if ((start = html_template.rfind(YPARSER_ESCAPE_START, end)) != std::string::npos) // 2. find next y-start befor { if (ydebug) hh->printf("[ycgi debug]: START at:%d following:%s<br/>\n", start, (html_template.substr(start + esc_len, 10)).c_str()); ycmd = html_template.substr(start + esc_len, end - (start + esc_len)); //3. get cmd if (ydebug) hh->printf("[ycgi debug]: CMD:[%s]<br/>\n", ycmd.c_str()); yresult = YWeb_cgi_cmd(hh, ycmd); // 4. execute cmd log_level_printf(5, "<yLoop>: ycmd...:%s\n", ycmd.c_str()); log_level_printf(6, "<yLoop>: yresult:%s\n", yresult.c_str()); if (ydebug) hh->printf("[ycgi debug]: RESULT:[%s]<br/>\n", yresult.c_str()); html_template.replace(start, end - start + esc_len, yresult); // 5. replace cmd with output is_cmd = true; // one command found if (ydebug) hh->printf("[ycgi debug]: STEP<br/>\n%s<br/>\n", html_template.c_str()); } } } while (is_cmd); return html_template; }
//------------------------------------------------------------------------- // y-func : dispatching and executing //------------------------------------------------------------------------- std::string CNeutrinoYParser::YWeb_cgi_func(CyhookHandler *hh, std::string ycmd) { std::string func="", para="", yresult="ycgi func not found"; bool found = false; ySplitString(ycmd," ",func, para); log_level_printf(4,"NeutrinoYParser: func:(%s)\n", func.c_str()); for(unsigned int i = 0;i < (sizeof(yFuncCallList)/sizeof(yFuncCallList[0])); i++) if (func == yFuncCallList[i].func_name) { yresult = (this->*yFuncCallList[i].pfunc)(hh, para); found = true; break; } log_level_printf(8,"NeutrinoYParser: func:(%s) para:(%s) Result:(%s)\n", func.c_str(), para.c_str(), yresult.c_str() ); if(!found) yresult = CyParser::YWeb_cgi_func(hh, ycmd); return yresult; }
//----------------------------------------------------------------------------- // Hook Dispatcher for UploadSetFilename //----------------------------------------------------------------------------- THandleStatus CyhookHandler::Hooks_UploadReady(const std::string& Filename) { log_level_printf(4, "UploadReady Hook-List Start. Filename:(%s)\n", Filename.c_str()); THandleStatus _status = HANDLED_NONE; THookList::iterator i = HookList.begin(); for (; i != HookList.end(); i++) { log_level_printf(4, "UploadReady Hook-List (%s) Start\n", ((*i)->getHookName()).c_str()); // response Hook _status = (*i)->Hook_UploadReady(this, Filename); log_level_printf(4, "UploadReady Hook-List (%s) End. Status (%d)\n", ((*i)->getHookName()).c_str(), _status); if ((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE)) break; } log_level_printf(4, "UploadReady Hook-List End\n"); status = _status; return _status; }
//----------------------------------------------------------------------------- // HOOK: Response Prepare Handler // Response Prepare Check. // Is it in cache? //----------------------------------------------------------------------------- THandleStatus CmodCache::Hook_PrepareResponse(CyhookHandler *hh) { hh->status = HANDLED_NONE; log_level_printf(4,"mod_cache prepare hook start url:%s\n",hh->UrlData["fullurl"].c_str()); std::string url = hh->UrlData["fullurl"]; if(CacheList.find(url) != CacheList.end()) // is in Cache. Rewrite URL or not modified { pthread_mutex_lock(&mutex); // yeah, its mine // Check if modified time_t if_modified_since = (time_t)-1; if(!hh->HeaderList["If-Modified-Since"].empty()) // Have If-Modified-Since Requested by Browser? { struct tm mod; if(strptime(hh->HeaderList["If-Modified-Since"].c_str(), RFC1123FMT, &mod) != NULL) { mod.tm_isdst = 0; // daylight saving flag! if_modified_since = mktime(&mod); // Date given } } // normalize obj_last_modified to GMT struct tm lt; gmtime_r(&CacheList[url].created, <); time_t obj_last_modified_gmt = mktime(<); bool modified = (if_modified_since == (time_t)-1) || (if_modified_since < obj_last_modified_gmt); // Send file or not-modified header if(modified) { hh->SendFile(CacheList[url].filename); hh->ResponseMimeType = CacheList[url].mime_type; } else hh->SetHeader(HTTP_NOT_MODIFIED, CacheList[url].mime_type, HANDLED_READY); pthread_mutex_unlock(&mutex); } log_level_printf(4,"mod_cache hook prepare end status:%d\n",(int)hh->status); return hh->status; }
//----------------------------------------------------------------------------- // Add Socket fd to FD_SET again (for select-handling) // Add start-time for waiting for connection re-use / keep-alive //----------------------------------------------------------------------------- void CWebserver::addSocketToMasterSet(SOCKET fd) { int slot = SL_GetExistingSocket(fd); // get slot/index for fd if (slot < 0) return; log_level_printf(2, "FD: add to master fd:%d\n", fd); struct timeval tv_now; struct timezone tz_now; gettimeofday(&tv_now, &tz_now); SocketList[slot]->tv_start_waiting = tv_now; // add keep-alive wait time FD_SET(fd, &master); // add fd to select-master-set }
//----------------------------------------------------------------------------- // Receive File over Socket for FILE* filed // read/write in small blocks (to save memory). // usind sleep for sync input // fd is an opened FILE-Descriptor //----------------------------------------------------------------------------- //TODO: Write upload Progress Informations into a file unsigned int CySocket::ReceiveFileGivenLength(int filed, unsigned int _length) { unsigned int _readbytes = 0; char buffer[RECEIVE_BLOCK_LEN]; int retries=0; do { // check bytes in Socket buffer u_long readarg = 0; #ifdef Y_CONFIG_USE_OPEN_SSL if(isSSLSocket) readarg = RECEIVE_BLOCK_LEN; else #endif { if(ioctl(sock, FIONREAD, &readarg) != 0)// How many bytes avaiable on socket? break; if(readarg > RECEIVE_BLOCK_LEN) // enough bytes to read readarg = RECEIVE_BLOCK_LEN; // read only given length } if(readarg == 0) // nothing to read: sleep { retries++; if(retries >NON_BLOCKING_MAX_RETRIES) break; sleep(1); } else { int bytes_gotten = Read(buffer, readarg); if(bytes_gotten == -1 && errno == EINTR)// non-blocking continue; if(bytes_gotten <= 0) // ERROR Code gotten or Conection closed by peer { isValid = false; break; } _readbytes += bytes_gotten; if (write(filed, buffer, bytes_gotten) != bytes_gotten) { perror("write file failed\n"); return 0; } retries = 0; if(bytes_gotten < NON_BLOCKING_TRY_BYTES) // to few bytes gotten: sleep sleep(1); } log_level_printf(8,"Receive Block length:%d all:%d\n",_readbytes, _length); } while(_readbytes + RECEIVE_BLOCK_LEN < _length); return _readbytes; }
//----------------------------------------------------------------------------- // BASIC Send over Socket for Strings (char*) //----------------------------------------------------------------------------- bool CWebserverResponse::WriteData(char const * data, long length) { if(Connection->RequestCanceled) return false; if(Connection->sock->Send(data, length) == -1) { log_level_printf(1,"response canceled: %s\n", strerror(errno)); Connection->RequestCanceled = true; return false; } else return true; }
//----------------------------------------------------------------------------- // Hook Dispatcher for Session Hooks // Execute every Hook in HookList until State change != HANDLED_NONE //----------------------------------------------------------------------------- THandleStatus CyhookHandler::Hooks_PrepareResponse() { log_level_printf(4, "PrepareResponse Hook-List Start\n"); THandleStatus _status = HANDLED_NONE; THookList::iterator i = HookList.begin(); for (; i != HookList.end(); i++) { log_level_printf(4, "PrepareResponse Hook-List (%s) Start\n", ((*i)->getHookName()).c_str()); // response Hook _status = (*i)->Hook_PrepareResponse(this); log_level_printf( 4, "PrepareResponse Hook-List (%s) End. Status (%d) HTTP Status (%d)\n", ((*i)->getHookName()).c_str(), status, httpStatus); if ((_status != HANDLED_NONE) && (_status != HANDLED_CONTINUE)) break; } log_level_printf(4, "PrepareResponse Hook-List End\n"); log_level_printf(8, "PrepareResponse Hook-List Result:\n%s\n", yresult.c_str()); status = _status; return _status; }
//----------------------------------------------------------------------------- // parse the header of the request // from RFC 2616 / 4.2 Message Header: // message-header = field-name ":" [ field-value ] // field-name = token // field-value = *( field-content | LWS ) // field-content = <the OCTETs making up the field-value // and consisting of either *TEXT or combinations // of token, separators, and quoted-string> //----------------------------------------------------------------------------- bool CWebserverRequest::ParseHeader(std::string header) { bool ende = false; std::string sheader, name, value; HeaderList.clear(); while (!ende) { if (!ySplitStringExact(header, "\r\n", sheader, header)) ende = true; if (ySplitStringExact(sheader, ":", name, value)) HeaderList[name] = trim(value); log_level_printf(8, "ParseHeader: name: %s value: %s\n", name.c_str(), value.c_str()); } return true; }
//============================================================================= // Hooks! //============================================================================= //----------------------------------------------------------------------------- // HOOK: response_hook Handler // This is the main dispatcher for this module //----------------------------------------------------------------------------- THandleStatus CNeutrinoYParser::Hook_SendResponse(CyhookHandler *hh) { hh->status = HANDLED_NONE; log_level_printf(4,"Neutrinoparser Hook Start url:%s\n",hh->UrlData["url"].c_str()); init(hh); CNeutrinoYParser *yP = new CNeutrinoYParser(NeutrinoAPI); // create a Session if (hh->UrlData["fileext"] == "yhtm" || hh->UrlData["fileext"] == "yjs" || hh->UrlData["fileext"] == "ysh") // yParser for y*-Files yP->ParseAndSendFile(hh); else if(hh->UrlData["path"] == "/y/") // /y/<cgi> commands { yP->Execute(hh); if(hh->status == HANDLED_NOT_IMPLEMENTED) hh->status = HANDLED_NONE; // y-calls can be implemented anywhere } delete yP; log_level_printf(4,"Neutrinoparser Hook Ende status:%d\n",(int)hh->status); // log_level_printf(5,"Neutrinoparser Hook Result:%s\n",hh->yresult.c_str()); return hh->status; }
//----------------------------------------------------------------------------- // URL Function Dispatching //----------------------------------------------------------------------------- void CyParser::Execute(CyhookHandler *hh) { int index = -1; std::string filename = hh->UrlData["filename"]; log_level_printf(4, "yParser.Execute filename%s\n", filename.c_str()); filename = string_tolower(filename); // debugging informations if (CLogging::getInstance()->getDebug()) { dprintf("Execute CGI : %s\n", filename.c_str()); for (CStringList::iterator it = hh->ParamList.begin(); it != hh->ParamList.end(); it++) dprintf(" Parameter %s : %s\n", it->first.c_str(), it->second.c_str()); } // get function index for (unsigned int i = 0; i < (sizeof(yCgiCallList) / sizeof(yCgiCallList[0])); i++) if (filename == yCgiCallList[i].func_name) { index = i; break; } if (index == -1) // function not found { hh->SetError(HTTP_NOT_IMPLEMENTED, HANDLED_NOT_IMPLEMENTED); return; } // send header if (std::string(yCgiCallList[index].mime_type) == "") // set by self ; else if (std::string(yCgiCallList[index].mime_type) == "+xml") // Parameter xml? if (hh->ParamList["xml"] != "") hh->SetHeader(HTTP_OK, "text/xml"); else hh->SetHeader(HTTP_OK, "text/plain"); else hh->SetHeader(HTTP_OK, yCgiCallList[index].mime_type); // response hh->status = HANDLED_READY; if (hh->Method == M_HEAD) // HEAD or function call return; else { (this->*yCgiCallList[index].pfunc)(hh); return; } }
std::string CyParser::func_do_reload_httpd_config(CyhookHandler *, std::string) { log_level_printf(1, "func_do_reload_httpd_config: raise USR1 !!!\n"); //raise(SIGUSR1); // Send HUP-Signal to Reload Settings yhttpd_reload_config(); return ""; }
//============================================================================= // Main Dispacher for Response // To understand HOOKS reade yhook.cpp Comments!!! //----------------------------------------------------------------------------- // RFC 2616 / 6 Response // // After receiving and interpreting a request message, a server responds // with an HTTP response message. // Response =Status-Line ; generated by SendHeader // *(( general-header ; generated by SendHeader // | response-header ; generated by SendHeader // | entity-header ) CRLF); generated by SendHeader // CRLF ; generated by SendHeader // [ message-body ] ; by HOOK Handling Loop or Sendfile //============================================================================= bool CWebserverResponse::SendResponse() { // Init Hookhandler Connection->HookHandler.session_init(Connection->Request.ParameterList, Connection->Request.UrlData, (Connection->Request.HeaderList), (Cyhttpd::ConfigList), Connection->Method, Connection->keep_alive); //-------------------------------------------------------------- // HOOK Handling Loop [ PREPARE response hook ] // Checking and Preperation: Auth, static, cache, ... //-------------------------------------------------------------- do { if(Connection->RequestCanceled) return false; Connection->HookHandler.Hooks_PrepareResponse(); if(Connection->HookHandler.status == HANDLED_ERROR || Connection->HookHandler.status == HANDLED_ABORT) { log_level_printf(2,"Response Prepare Hook found but Error\n"); Write(Connection->HookHandler.BuildHeader()); Write(Connection->HookHandler.yresult); return false; } // URL has new value. Analyze new URL for SendFile else if(Connection->HookHandler.status == HANDLED_SENDFILE || Connection->HookHandler.status == HANDLED_REWRITE) { Connection->Request.analyzeURL(Connection->HookHandler.NewURL); Connection->HookHandler.UrlData = Connection->Request.UrlData; } if(Connection->HookHandler.status == HANDLED_REDIRECTION) { Write(Connection->HookHandler.BuildHeader()); return false; } } while(Connection->HookHandler.status == HANDLED_REWRITE); // Prepare = NOT_MODIFIED ? if(Connection->HookHandler.httpStatus == HTTP_NOT_MODIFIED) { Write(Connection->HookHandler.BuildHeader()); return true; } //-------------------------------------------------------------- // HOOK Handling Loop [ response hook ] // Production //-------------------------------------------------------------- if(Connection->HookHandler.status != HANDLED_SENDFILE) do { if(Connection->RequestCanceled) return false; Connection->HookHandler.Hooks_SendResponse(); if((Connection->HookHandler.status == HANDLED_READY)||(Connection->HookHandler.status == HANDLED_CONTINUE)) { log_level_printf(2,"Response Hook Output. Status:%d\n", Connection->HookHandler.status); Write(Connection->HookHandler.BuildHeader()); if(Connection->Method != M_HEAD) Write(Connection->HookHandler.yresult); if(Connection->HookHandler.status != HANDLED_CONTINUE) return true; } else if(Connection->HookHandler.status == HANDLED_ERROR) { log_level_printf(2,"Response Hook found but Error\n"); Write(Connection->HookHandler.BuildHeader()); if(Connection->Method != M_HEAD) Write(Connection->HookHandler.yresult); return false; } else if(Connection->HookHandler.status == HANDLED_ABORT) return false; // URL has new value. Analyze new URL for SendFile else if(Connection->HookHandler.status == HANDLED_SENDFILE || Connection->HookHandler.status == HANDLED_REWRITE) { Connection->Request.analyzeURL(Connection->HookHandler.NewURL); Connection->HookHandler.UrlData = Connection->Request.UrlData; } if(Connection->HookHandler.status == HANDLED_REDIRECTION) { Write(Connection->HookHandler.BuildHeader()); return false; } } while(Connection->HookHandler.status == HANDLED_REWRITE); // Send static file if(Connection->HookHandler.status == HANDLED_SENDFILE && !Connection->RequestCanceled) { bool cache = true; // if(Connection->HookHandler.UrlData["path"] == "/tmp/")//TODO: un-cachable dirs // cache = false; Write(Connection->HookHandler.BuildHeader(cache)); if(Connection->Method != M_HEAD) Sendfile(Connection->Request.UrlData["url"]); return true; } // arrived here? = error! SendError(HTTP_NOT_FOUND); return false; }
//============================================================================= // Main Dispacher for Response // To understand HOOKS reade yhook.cpp Comments!!! //----------------------------------------------------------------------------- // RFC 2616 / 6 Response // // After receiving and interpreting a request message, a server responds // with an HTTP response message. // Response =Status-Line ; generated by SendHeader // *(( general-header ; generated by SendHeader // | response-header ; generated by SendHeader // | entity-header ) CRLF); generated by SendHeader // CRLF ; generated by SendHeader // [ message-body ] ; by HOOK Handling Loop or Sendfile //============================================================================= bool CWebserverResponse::SendResponse() { // Init Hookhandler Connection->HookHandler.session_init(Connection->Request.ParameterList, Connection->Request.UrlData, (Connection->Request.HeaderList), (Cyhttpd::ConfigList), Connection->Method, Connection->keep_alive); //-------------------------------------------------------------- // HOOK Handling Loop [ PREPARE response hook ] // Checking and Preperation: Auth, static, cache, ... //-------------------------------------------------------------- // move to mod_sendfile ??? #ifdef Y_CONFIG_USE_HOSTEDWEB // for hosted webs: rewrite URL std::string _hosted="/hosted/"; if((Connection->Request.UrlData["path"]).compare(0,_hosted.length(),"/hosted/") == 0) // hosted Web ? Connection->Request.UrlData["path"]=Cyhttpd::ConfigList["HostedDocumentRoot"] +(Connection->Request.UrlData["path"]).substr(_hosted.length()-1); #endif //Y_CONFIG_USE_HOSTEDWEB log_level_printf(5,"UrlPath:%s\n",(Connection->Request.UrlData["path"]).c_str()); do { if(Connection->RequestCanceled) return false; Connection->HookHandler.Hooks_PrepareResponse(); if(Connection->HookHandler.status == HANDLED_ERROR || Connection->HookHandler.status == HANDLED_ABORT) { log_level_printf(2,"Response Prepare Hook found but Error\n"); Write(Connection->HookHandler.BuildHeader()); Write(Connection->HookHandler.yresult); return false; } // URL has new value. Analyze new URL for SendFile else if(Connection->HookHandler.status == HANDLED_SENDFILE || Connection->HookHandler.status == HANDLED_REWRITE) { Connection->Request.analyzeURL(Connection->HookHandler.NewURL); Connection->HookHandler.UrlData = Connection->Request.UrlData; } if(Connection->HookHandler.status == HANDLED_REDIRECTION) { Write(Connection->HookHandler.BuildHeader()); return false; } } while(Connection->HookHandler.status == HANDLED_REWRITE); // Prepare = NOT_MODIFIED ? if(Connection->HookHandler.httpStatus == HTTP_NOT_MODIFIED) { Write(Connection->HookHandler.BuildHeader()); return true; } //-------------------------------------------------------------- // HOOK Handling Loop [ response hook ] // Production //-------------------------------------------------------------- if(Connection->HookHandler.status != HANDLED_SENDFILE) do { if(Connection->RequestCanceled) return false; Connection->HookHandler.Hooks_SendResponse(); if((Connection->HookHandler.status == HANDLED_READY)||(Connection->HookHandler.status == HANDLED_CONTINUE)) { log_level_printf(2,"Response Hook Output. Status:%d\n", Connection->HookHandler.status); Write(Connection->HookHandler.BuildHeader()); if(Connection->Method != M_HEAD) Write(Connection->HookHandler.yresult); if(Connection->HookHandler.status != HANDLED_CONTINUE) return true; } else if(Connection->HookHandler.status == HANDLED_ERROR) { log_level_printf(2,"Response Hook found but Error\n"); Write(Connection->HookHandler.BuildHeader()); if(Connection->Method != M_HEAD) Write(Connection->HookHandler.yresult); return false; } else if(Connection->HookHandler.status == HANDLED_ABORT) return false; // URL has new value. Analyze new URL for SendFile else if(Connection->HookHandler.status == HANDLED_SENDFILE || Connection->HookHandler.status == HANDLED_REWRITE) { Connection->Request.analyzeURL(Connection->HookHandler.NewURL); Connection->HookHandler.UrlData = Connection->Request.UrlData; } if(Connection->HookHandler.status == HANDLED_REDIRECTION) { Write(Connection->HookHandler.BuildHeader()); return false; } } while(Connection->HookHandler.status == HANDLED_REWRITE); // Send static file if(Connection->HookHandler.status == HANDLED_SENDFILE && !Connection->RequestCanceled) { bool cache = true; // if(Connection->HookHandler.UrlData["path"] == "/tmp/")//TODO: un-cachable dirs // cache = false; Write(Connection->HookHandler.BuildHeader(cache)); if(Connection->Method != M_HEAD) Sendfile(Connection->Request.UrlData["url"]); return true; } // arrived here? = error! SendError(HTTP_NOT_FOUND); return false; }
//------------------------------------------------------------------------- void CWebserverConnection::ShowEnlapsedRequest(char *text) { long enlapsed = GetEnlapsedRequestTime() / 1000; log_level_printf(1, "enlapsed-f-start (%s) t:%ld url:%s\n", text, enlapsed, (Request.UrlData["fullurl"]).c_str()); }
bool CWebserver::run(void) { set_threadname(__func__); if (!listenSocket.listen(port, HTTPD_MAX_CONNECTIONS)) { if (port != 80) { fprintf(stderr, "[yhttpd] Socket cannot bind and listen on port %d Abort.\n", port); return false; } fprintf(stderr, "[yhttpd] cannot bind and listen on port 80, retrying on port 8080.\n"); port = 8080; if (!listenSocket.listen(port, HTTPD_MAX_CONNECTIONS)) { fprintf(stderr, "[yhttpd] Socket cannot bind and listen on port %d Abort.\n", port); return false; } } #ifdef Y_CONFIG_FEATURE_KEEP_ALIVE // initialize values for select int listener = listenSocket.get_socket();// Open Listener struct timeval tv; // timeout struct FD_SET(listener, &master); // add the listener to the master set fdmax = listener; // init max fd fcntl(listener, F_SETFD , O_NONBLOCK); // listener master socket non-blocking int timeout_counter = 0; // Counter for Connection Timeout checking int test_counter = 0; // Counter for Testing long running Connections // main Webserver Loop while(!terminate) { // select : init vars read_fds = master; // copy it tv.tv_usec = 10000; // microsec: Timeout for select ! for re-use / keep-alive socket tv.tv_sec = 0; // seconds int fd = -1; // select : wait for socket activity if(open_connections <= 0) // No open Connection. Wait in select. fd = select(fdmax+1,&read_fds, NULL, NULL, NULL);// wait for socket activity else fd = select(fdmax+1,&read_fds, NULL, NULL, &tv);// wait for socket activity or timeout // too much to do : sleep if(open_connections >= HTTPD_MAX_CONNECTIONS-1) sleep(1); // Socket Error? if(fd == -1 && errno != EINTR) { perror("select"); return false; } // Socket Timeout? if(fd == 0) { // Testoutput for long living threads if(++test_counter >= MAX_TIMEOUTS_TO_TEST) { for(int j=0;j < HTTPD_MAX_CONNECTIONS;j++) if(SocketList[j] != NULL) // here is a socket log_level_printf(2,"FD-TEST sock:%d handle:%d open:%d\n",SocketList[j]->get_socket(), SocketList[j]->handling,SocketList[j]->isOpened); test_counter=0; } // some connection closing previous missed? if(++timeout_counter >= MAX_TIMEOUTS_TO_CLOSE) { CloseConnectionSocketsByTimeout(); timeout_counter=0; } continue; // main loop again } //---------------------------------------------------------------------------------------- // Check all observed descriptors & check new or re-use Connections //---------------------------------------------------------------------------------------- for(int i = listener; i <= fdmax; i++) { int slot = -1; if(FD_ISSET(i, &read_fds)) // Socket observed? { // we got one!! if (i == listener) // handle new connections slot = AcceptNewConnectionSocket(); else // Connection on an existing open Socket = reuse (keep-alive) { slot = SL_GetExistingSocket(i); if(slot>=0) log_level_printf(2,"FD: reuse con fd:%d\n",SocketList[slot]->get_socket()); } // prepare Connection handling if(slot>=0) if(SocketList[slot] != NULL && !SocketList[slot]->handling && SocketList[slot]->isValid) { log_level_printf(2,"FD: START CON HANDLING con fd:%d\n",SocketList[slot]->get_socket()); FD_CLR(SocketList[slot]->get_socket(), &master); // remove from master set SocketList[slot]->handling = true; // prepares for thread-handling if(!handle_connection(SocketList[slot]))// handle this activity { // Can not handle more threads char httpstr[]=HTTP_PROTOCOL " 503 Service Unavailable\r\n\r\n"; SocketList[slot]->Send(httpstr, strlen(httpstr)); SL_CloseSocketBySlot(slot); } } } }// for CloseConnectionSocketsByTimeout(); // Check connections to close }//while #else while (!terminate) { CySocket *newConnectionSock; if (!(newConnectionSock = listenSocket.accept())) //Now: Blocking wait { pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); pthread_testcancel(); dperror("Socket accept error. Continue.\n"); continue; } log_level_printf(3, "Socket connect from %s\n", (listenSocket.get_client_ip()).c_str()); #ifdef Y_CONFIG_USE_OPEN_SSL if(Cyhttpd::ConfigList["SSL"]=="true") newConnectionSock->initAsSSL(); // make it a SSL-socket #endif handle_connection(newConnectionSock); } #endif return true; }