/**************************************************************************************************** RepRapFirmware - Webserver This class serves a single-page web applications to the attached network. This page forms the user's interface with the RepRap machine. This software interprests returned values from the page and uses it to Generate G Codes, which it sends to the RepRap. It also collects values from the RepRap like temperature and uses those to construct the web page. The page itself - reprap.htm - uses Knockout.js and Jquery.js. See: http://knockoutjs.com/ http://jquery.com/ ----------------------------------------------------------------------------------------------------- Version 0.2 10 May 2013 Adrian Bowyer RepRap Professional Ltd http://reprappro.com Licence: GPL ****************************************************************************************************/ #include "RepRapFirmware.h" //*************************************************************************************************** boolean Webserver::MatchBoundary(char c) { if(!postBoundary[0]) return false; if(c == postBoundary[boundaryCount]) { boundaryCount++; if(!postBoundary[boundaryCount]) { boundaryCount = 0; return true; } } else { for(int i = 0; i < boundaryCount; i++) platform->Write(postFile, postBoundary[i]); platform->Write(postFile, c); boundaryCount = 0; } return false; } //**************************************************************************************************** // Feeding G Codes to the GCodes class boolean Webserver::Available() { return gcodeAvailable; } byte Webserver::Read() { byte c = gcodeBuffer[gcodePointer]; if(!c) { gcodeAvailable = false; gcodePointer = 0; gcodeBuffer[gcodePointer] = 0; } else gcodePointer++; return c; } boolean Webserver::LoadGcodeBuffer(char* gc, boolean convertWeb) { if(gcodeAvailable) return false; if(strlen(gc) > GCODE_LENGTH-1) { platform->Message(HOST_MESSAGE, "Webserver: GCode buffer overflow.
\n"); return false; } int gcp = 0; gcodePointer = 0; gcodeBuffer[gcodePointer] = 0; char c; while(c = gc[gcp++]) { if(c == '+' && convertWeb) c = ' '; if(c == '%'&& convertWeb) { c = 0; if(isalpha(gc[gcp])) c += 16*(gc[gcp] - 'A' + 10); else c += 16*(gc[gcp] - '0'); gcp++; if(isalpha(gc[gcp])) c += gc[gcp] - 'A' + 10; else c += gc[gcp] - '0'; gcp++; } gcodeBuffer[gcodePointer++] = c; } while(isspace(gcodeBuffer[gcodePointer - 1]) && gcodePointer > 0) // Kill any trailing space gcodePointer--; gcodeBuffer[gcodePointer] = 0; gcodePointer = 0; // We intercept two G Codes so we can deal with file manipulation. That // way things don't get out of sync, and - as a file name can contain // a valid G code (!) - confusion is avoided. char fileAct = 0; if(StringStartsWith(gcodeBuffer, "M30 ")) fileAct |= 1; if(StringStartsWith(gcodeBuffer, "M23 ")) fileAct |= 2; if(fileAct) // Delete or print a file? { if(fileAct == 1) // Delete? { if(!platform->DeleteFile(platform->PrependRoot(platform->GetGcodeDir(), &gcodeBuffer[4]))) { platform->Message(HOST_MESSAGE, "Unsuccsessful attempt to delete: "); platform->Message(HOST_MESSAGE, &gcodeBuffer[4]); platform->Message(HOST_MESSAGE, "
\n"); } } else // Print it { reprap.GetGCodes()->QueueFileToPrint(platform->PrependRoot(platform->GetGcodeDir(), &gcodeBuffer[4])); } // Check for further G Codes in the string gcodePointer = 0; while(gcodeBuffer[gcodePointer]) { if(gcodeBuffer[gcodePointer] == '\n') { gcodeAvailable = true; return true; } gcodePointer++; } gcodePointer = 0; gcodeBuffer[gcodePointer] = 0; gcodeAvailable = false; return true; } // Otherwise, send them to the G Code interpreter gcodeAvailable = true; return true; } //******************************************************************************************** // Communications with the client //-------------------------------------------------------------------------------------------- // Output to the client void Webserver::CloseClient() { writing = false; //inPHPFile = false; //InitialisePHP(); clientCloseTime = platform->Time(); needToCloseClient = true; } void Webserver::SendFile(char* nameOfFileToSend) { char sLen[POST_LENGTH]; boolean zip = false; // if(!gotPassword) // { // sendTable = false; // nameOfFileToSend = PASSWORD_PAGE; // } else sendTable = true; /* if(StringEndsWith(nameOfFileToSend, ".js")) { len = strlen(nameOfFileToSend); nameOfFileToSend[len-2] = 'z'; nameOfFileToSend[len-1] = 'i'; nameOfFileToSend[len] = 'p'; nameOfFileToSend[len+1] = 0; }*/ if(StringStartsWith(nameOfFileToSend, KO_START)) GetJsonResponse(&nameOfFileToSend[KO_FIRST]); if(jsonPointer < 0) { fileBeingSent = platform->OpenFile(platform->PrependRoot(platform->GetWebDir(), nameOfFileToSend), false); if(fileBeingSent < 0) { sendTable = false; nameOfFileToSend = FOUR04_FILE; fileBeingSent = platform->OpenFile(platform->PrependRoot(platform->GetWebDir(), nameOfFileToSend), false); } //inPHPFile = StringEndsWith(nameOfFileToSend, ".php"); //if(inPHPFile) // InitialisePHP(); writing = true; } //if(jsonPointer >=0) // platform->SendToClient("HTTP/1.1 201 OK\n"); //else platform->SendToClient("HTTP/1.1 200 OK\n"); platform->SendToClient("Content-Type: "); if(StringEndsWith(nameOfFileToSend, ".png")) platform->SendToClient("image/png\n"); else if(StringEndsWith(nameOfFileToSend, ".ico")) platform->SendToClient("image/x-icon\n"); else if (jsonPointer >=0) platform->SendToClient("application/json\n"); else if(StringEndsWith(nameOfFileToSend, ".js")) platform->SendToClient("application/javascript\n"); else if(StringEndsWith(nameOfFileToSend, ".zip")) { platform->SendToClient("application/zip\n"); zip = true; } else platform->SendToClient("text/html\n"); if (jsonPointer >=0) { platform->SendToClient("Content-Length: "); sprintf(sLen, "%d", strlen(jsonResponse)); platform->SendToClient(sLen); platform->SendToClient("\n"); } if(zip) { platform->SendToClient("Content-Encoding: gzip\n"); platform->SendToClient("Content-Length: "); sprintf(sLen, "%llu", platform->Length(fileBeingSent)); platform->SendToClient(sLen); platform->SendToClient("\n"); } platform->SendToClient("Connnection: close\n"); platform->SendToClient('\n'); } void Webserver::WriteByte() { unsigned char b; if(jsonPointer >= 0) { if(jsonResponse[jsonPointer]) platform->SendToClient(jsonResponse[jsonPointer++]); else { jsonPointer = -1; jsonResponse[0] = 0; CloseClient(); } } else { if(platform->Read(fileBeingSent, b)) platform->SendToClient(b); else { platform->Close(fileBeingSent); CloseClient(); } } } //---------------------------------------------------------------------------------------------------- // Input from the client void Webserver::CheckPassword() { gotPassword = StringEndsWith(clientQualifier, password); } void Webserver::GetJsonResponse(char* request) { jsonPointer = 0; writing = true; boolean ok = false; if(StringStartsWith(request, "name")) { strcpy(jsonResponse, "{\"myName\":\""); strcat(jsonResponse, myName); strcat(jsonResponse, "\"}"); ok = true; } if(StringStartsWith(request, "password")) { CheckPassword(); strcpy(jsonResponse, "{\"password\":\""); if(gotPassword) strcat(jsonResponse, "right"); else strcat(jsonResponse, "wrong"); strcat(jsonResponse, "\"}"); ok = true; } if(StringStartsWith(request, "gcode")) { if(!LoadGcodeBuffer(&clientQualifier[6], true)) platform->Message(HOST_MESSAGE, "Webserver: buffer not free!
\n"); strcpy(jsonResponse, "{}"); ok = true; } if(StringStartsWith(request, "files")) { char* fileList = platform->FileList(platform->GetGcodeDir()); strcpy(jsonResponse, "{\"files\":["); strcat(jsonResponse, fileList); strcat(jsonResponse, "]}"); ok = true; } if(ok) { platform->Message(HOST_MESSAGE, "JSON response: "); platform->Message(HOST_MESSAGE, jsonResponse); platform->Message(HOST_MESSAGE, " queued
\n"); } else { platform->Message(HOST_MESSAGE, "KnockOut request: "); platform->Message(HOST_MESSAGE, request); platform->Message(HOST_MESSAGE, " not recognised
\n"); clientRequest[0] = 0; } } /* Parse a string in clientLine[] from the user's web browser Simple requests have the form: GET /page2.htm HTTP/1.1 ^ Start clientRequest[] at clientLine[5]; stop at the blank or... ...fancier ones with arguments after a '?' go: GET /gather.asp?pwd=my_pwd HTTP/1.1 ^ Start clientRequest[] ^ Start clientQualifier[] */ void Webserver::ParseGetPost() { if(reprap.debug()) { platform->Message(HOST_MESSAGE, "HTTP request: "); platform->Message(HOST_MESSAGE, clientLine); platform->Message(HOST_MESSAGE, "
\n"); } int i = 5; int j = 0; clientRequest[j] = 0; clientQualifier[0] = 0; while(clientLine[i] != ' ' && clientLine[i] != '?') { clientRequest[j] = clientLine[i]; j++; i++; } clientRequest[j] = 0; if(clientLine[i] == '?') { i++; j = 0; while(clientLine[i] != ' ') { clientQualifier[j] = clientLine[i]; j++; i++; } clientQualifier[j] = 0; } } void Webserver::InitialisePost() { postSeen = false; receivingPost = false; boundaryCount = 0; postBoundary[0] = 0; postFileName[0] = 0; postFile = -1; } void Webserver::ParseClientLine() { if(StringStartsWith(clientLine, "GET")) { ParseGetPost(); postSeen = false; getSeen = true; if(!clientRequest[0]) strcpy(clientRequest, INDEX_PAGE); return; } if(StringStartsWith(clientLine, "POST")) { ParseGetPost(); InitialisePost(); postSeen = true; getSeen = false; if(!clientRequest[0]) strcpy(clientRequest, INDEX_PAGE); return; } int bnd; if(postSeen && ( (bnd = StringContains(clientLine, "boundary=")) >= 0) ) { if(strlen(&clientLine[bnd]) >= POST_LENGTH - 4) { platform->Message(HOST_MESSAGE, "Post boundary buffer overflow.
\n"); return; } postBoundary[0] = '-'; postBoundary[1] = '-'; strcpy(&postBoundary[2], &clientLine[bnd]); strcat(postBoundary, "--"); //Serial.print("Got boundary: "); //Serial.println(postBoundary); return; } if(receivingPost && StringStartsWith(clientLine, "Content-Disposition:")) { bnd = StringContains(clientLine, "filename=\""); if(bnd < 0) { platform->Message(HOST_MESSAGE, "Post disposition gives no filename.
\n"); return; } int i = 0; while(clientLine[bnd] && clientLine[bnd] != '"') { postFileName[i++] = clientLine[bnd++]; if(i >= POST_LENGTH) { i = 0; platform->Message(HOST_MESSAGE, "Post filename buffer overflow.
\n"); } } postFileName[i] = 0; //Serial.print("Got file name: "); //Serial.println(postFileName); return; } } /* void Webserver::ParseQualifier() { if(!clientQualifier[0]) return; // if(StringStartsWith(clientQualifier, "pwd=")) // CheckPassword(); // if(!gotPassword) //Doan work fur nuffink // return; if(StringStartsWith(clientQualifier, "gcode=")) { if(!LoadGcodeBuffer(&clientQualifier[6], true)) platform->Message(HOST_MESSAGE, "Webserver: buffer not free!
\n"); //strcpy(clientRequest, INDEX_PAGE); } } */ // if you've gotten to the end of the line (received a newline // character) and the line is blank, the http request has ended, // so you can send a reply void Webserver::BlankLineFromClient() { clientLine[clientLinePointer] = 0; clientLinePointer = 0; //ParseQualifier(); //Serial.println("End of header."); if(getSeen) { SendFile(clientRequest); clientRequest[0] = 0; return; } if(postSeen) { receivingPost = true; postSeen = false; return; } if(receivingPost) { postFile = platform->OpenFile(platform->PrependRoot(platform->GetGcodeDir(), postFileName), true); if(postFile < 0 || !postBoundary[0]) { platform->Message(HOST_MESSAGE, "Can't open file for write or no post boundary: "); platform->Message(HOST_MESSAGE, platform->PrependRoot(platform->GetGcodeDir(), postFileName)); platform->Message(HOST_MESSAGE, "
\n"); InitialisePost(); } } } void Webserver::CharFromClient(char c) { if(c == '\n' && clientLineIsBlank) { BlankLineFromClient(); return; } if(c == '\n') { clientLine[clientLinePointer] = 0; ParseClientLine(); // you're starting a new line clientLineIsBlank = true; clientLinePointer = 0; } else if(c != '\r') { // you've gotten a character on the current line clientLineIsBlank = false; clientLine[clientLinePointer] = c; clientLinePointer++; if(clientLinePointer >= STRING_LENGTH) { platform->Message(HOST_MESSAGE,"Client read buffer overflow.
\n"); clientLinePointer = 0; clientLine[clientLinePointer] = 0; } } } // Deal with input/output from/to the client (if any) one byte at a time. void Webserver::Spin() { char sw[2]; if(!active) return; if(writing) { // if(inPHPFile) // WritePHPByte(); // else WriteByte(); return; } if(platform->ClientStatus() & CONNECTED) { if(platform->ClientStatus() & AVAILABLE) { char c = platform->ClientRead(); // if(echoInput) // { // Serial.print(c); //sw[0] = c; // sw[1] = 0; //platform->Message(HOST_MESSAGE, sw); //if(c == '\n') // platform->Message(HOST_MESSAGE, "i: "); // } if(receivingPost && postFile >= 0) { if(MatchBoundary(c)) { //Serial.println("Got to end of file."); platform->Close(postFile); SendFile(clientRequest); clientRequest[0] = 0; InitialisePost(); } return; } CharFromClient(c); } } if (platform->ClientStatus() & CLIENT) { if(needToCloseClient) { if(platform->Time() - clientCloseTime < CLIENT_CLOSE_DELAY) return; needToCloseClient = false; platform->DisconnectClient(); } } } //********************************************************************************************** /* // PHP interpreter void Webserver::InitialisePHP() { phpTag[0] = 0; inPHPString = 0; phpPointer = 0; phpEchoing = false; phpIfing = false; phpPrinting = false; eatInput = false; recordInput = false; phpRecordPointer = 0; phpRecord[phpRecordPointer] = 0; ifWasTrue = true; } char Webserver::PHPParse(char* phpString) { if(StringEquals(phpString, "if(")) return PHP_IF; if(StringEquals(phpString, "echo")) return PHP_ECHO; if(StringEquals(phpString, "print(")) return PHP_PRINT; return NO_PHP; } boolean Webserver::PrintLinkTable() { boolean r = sendTable; sendTable = true; return r; } boolean Webserver::CallPHPBoolean(char* phpRecord) { if(StringEquals(phpRecord, "gotPassword(")) return gotPassword; if(StringEquals(phpRecord, "printLinkTable(")) return PrintLinkTable(); platform->Message(HOST_MESSAGE, "callPHPBoolean(): non-existent function - "); platform->Message(HOST_MESSAGE, phpRecord); platform->Message(HOST_MESSAGE, "
\n"); return true; // Best default } void Webserver::GetGCodeList() { platform->SendToClient(platform->FileList(platform->GetGcodeDir())); } void Webserver::CallPHPString(char* phpRecord) { if(StringEquals(phpRecord, "getMyName(")) { platform->SendToClient(myName); return; } if(StringEquals(phpRecord, "getGCodeList(")) { GetGCodeList(); return; } if(StringEquals(phpRecord, "logout(")) { gotPassword = false; platform->SendToClient("SendToClient(PASSWORD_PAGE); platform->SendToClient("\">"); return; } platform->Message(HOST_MESSAGE, "callPHPString(): non-existent function - "); platform->Message(HOST_MESSAGE, phpRecord); platform->Message(HOST_MESSAGE, "
\n"); } void Webserver::ProcessPHPByte(char b) { if(eatInput) { if(b == eatInputChar) eatInput = false; return; } if(recordInput) { if(b == eatInputChar) { recordInput = false; phpRecordPointer = 0; return; } phpRecord[phpRecordPointer++] = b; if(phpRecordPointer >= PHP_TAG_LENGTH) { platform->Message(HOST_MESSAGE, "ProcessPHPByte: PHP record buffer overflow.
\n"); InitialisePHP(); } phpRecord[phpRecordPointer] = 0; return; } if(phpEchoing) { if(b != '\'') { if(ifWasTrue) platform->SendToClient(b); } else { InitialisePHP(); eatInput = true; eatInputChar = '>'; } return; } if(phpIfing) { boolean ifWas = CallPHPBoolean(phpRecord); InitialisePHP(); ifWasTrue = ifWas; inPHPString = 5; if(b != ')') { eatInput = true; eatInputChar = ')'; } return; } if(phpPrinting) { CallPHPString(phpRecord); InitialisePHP(); eatInput = true; eatInputChar = '>'; return; } if(inPHPString >= 5) { // We are in a PHP expression if(isspace(b)) return; phpTag[phpPointer++] = b; phpTag[phpPointer] = 0; if(phpPointer >= PHP_TAG_LENGTH) { platform->Message(HOST_MESSAGE, "ProcessPHPByte: PHP buffer overflow: "); platform->Message(HOST_MESSAGE, phpTag); platform->Message(HOST_MESSAGE, "
\n"); InitialisePHP(); return; } switch(PHPParse(phpTag)) { case PHP_ECHO: phpEchoing = true; eatInput = true; eatInputChar = '\''; break; case PHP_IF: phpIfing = true; recordInput = true; phpRecordPointer = 0; phpRecord[phpRecordPointer] = 0; eatInputChar = ')'; break; case PHP_PRINT: phpPrinting = true; recordInput = true; phpRecordPointer = 0; phpRecord[phpRecordPointer] = 0; eatInputChar = ')'; break; default: break; } return; } // We are not in a PHP expression phpTag[inPHPString] = b; switch(inPHPString) { case 0: if(b == '<') { inPHPString = 1; } else { phpTag[1] = 0; inPHPString = 0; platform->SendToClient(phpTag); } return; case 1: if(b == '?') { inPHPString = 2; } else { phpTag[2] = 0; inPHPString = 0; platform->SendToClient(phpTag); } return; case 2: if(b == 'p') { inPHPString = 3; } else { phpTag[3] = 0; inPHPString = 0; platform->SendToClient(phpTag); } return; case 3: if(b == 'h') { inPHPString = 4; } else { phpTag[4] = 0; inPHPString = 0; platform->SendToClient(phpTag); } return; case 4: if(b == 'p') { inPHPString = 5; phpTag[0] = 0; phpPointer = 0; } else { phpTag[5] = 0; inPHPString = 0; platform->SendToClient(phpTag); } return; // Should never get here... default: platform->Message(HOST_MESSAGE, "ProcessPHPByte: PHP tag runout.
\n"); platform->SendToClient(b); InitialisePHP(); } } void Webserver::WritePHPByte() { unsigned char b; if(platform->Read(fileBeingSent, b)) ProcessPHPByte(b); else { platform->Close(fileBeingSent); InitialisePHP(); CloseClient(); } } */ //****************************************************************************************** // Constructor and initialisation Webserver::Webserver(Platform* p) { //Serial.println("Webserver constructor"); platform = p; active = false; } void Webserver::Init() { lastTime = platform->Time(); writing = false; receivingPost = false; postSeen = false; getSeen = false; jsonPointer = -1; //postLength = 0L; //inPHPFile = false; //InitialisePHP(); clientLineIsBlank = true; needToCloseClient = false; clientLinePointer = 0; clientLine[0] = 0; clientRequest[0] = 0; password = DEFAULT_PASSWORD; myName = DEFAULT_NAME; gotPassword = false; gcodeAvailable = false; gcodePointer = 0; sendTable = true; //phpRecordPointer = 0; echoInput = false; echoOutput = false; InitialisePost(); active = true; } void Webserver::Exit() { active = false; }