#ifndef ECERE_NONET #include import "List" import "network" #ifndef ECERE_NOSSL import "SSLSocket" #endif class ConnectionsHolder { List connections { }; ~ConnectionsHolder() { HTTPConnection c; while((c = connections[0])) delete c; // The HTTPConnection destructor will take out from the list } } static ConnectionsHolder holder { }; namespace net; /*static */define HTTPFILE_BUFFERSIZE = 65536; static Mutex connectionsMutex { }; static class ServerNode : BTNode { char address[24]; bool resolved; ~ServerNode() { delete (char *)key; } } static class ServerNameCache { BinaryTree servers { CompareKey = (void *)BinaryTree::CompareString; }; Mutex mutex { }; ~ServerNameCache() { ServerNode server; while((server = (ServerNode)servers.root)) { servers.Remove(server); delete server; } } bool Resolve(const char * host, char * address) { ServerNode server; mutex.Wait(); server = (ServerNode)servers.FindString(host); if(!server) { server = ServerNode { key = (uintptr)CopyString(host) }; servers.Add(server); server.resolved = GetAddressFromName(host, server.address); } mutex.Release(); if(server.resolved) strcpy(address, server.address); return server.resolved; } }; static ServerNameCache serverNameCache { }; static const char * GetString(const char * string, const char * what, int count) { int c; for(c = 0; what[c]; c++) { if((count && c >= count) || (string[c] != what[c] && tolower(string[c]) != tolower(what[c]))) return null; } return string + c; } #ifdef ECERE_NOSSL private class HTTPConnection : Socket #else private class HTTPConnection : SSLSocket #endif { class_no_expansion; char * server; int port; HTTPFile file; bool secure; processAlone = true; ~HTTPConnection() { // printf("Before TakeOut we have %d connections:\n", holder.connections.count); for(c : holder.connections) ::PrintLn(c.server); ::PrintLn(""); holder.connections.TakeOut(this); /* PrintLn(server, " Connection Closed (", holder.connections.count, ") opened"); printf("Now we have %d connections:\n", holder.connections.count); for(c : holder.connections) { ::PrintLn(c.server); } ::PrintLn(""); */ delete server; } void OnDisconnect(int code) { connectionsMutex.Wait(); if(file) delete file.connection; // This decrements the file's reference connectionsMutex.Release(); // PrintLn(server, " Disconnected Us"); delete this; // The 'connections' reference } uint Open_OnReceive(const byte * buffer, uint count) { HTTPFile file = this.file; int pos = 0; int c; while(!file.done) { bool gotEndLine = false; for(c = 0; c<(int)count-1; c++) { if(buffer[c] == '\r' && buffer[c+1] == '\n') { gotEndLine = true; break; } } if(!gotEndLine) // Incomplete packet return pos; if(c 128) { byte nibble; msg[len++] = '%'; nibble = (ch & 0xF0) >> 4; msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0'); nibble = ch & 0x0F; msg[len++] = (nibble > 9) ? (nibble - 10 + 'a') : (nibble + '0'); } else msg[len++] = ch; } msg[len] = '\0'; } strcat(msg, " HTTP/1.1\r\nHost: "); //strcat(msg, " HTTP/1.0\r\nHost: "); strcat(msg, server); strcat(msg, "\r\n"); strcat(msg, "Accept-Charset: UTF-8\r\n"); //strcat(msg, "Accept-Charset: ISO-8859-1\r\n"); strcat(msg, "Connection: Keep-Alive\r\n"); if(referer) { strcat(msg, "Referer: "); strcat(msg, referer); strcat(msg, "\r\n"); } strcat(msg, "\r\n"); len = strlen(msg); //::PrintLn("Releasing connectionsMutex before GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID()); connectionsMutex.Release(); // ::PrintLn("Sending GET for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n"); connection.Send(msg, len); while(this.connection && this.connection.connected && !done) { this.connection.Process(); } //::PrintLn("Got DONE for ", name, " ", (uint64)this, " in thread ", GetCurrentThreadID(), "\n"); if(this.connection) { if(name != location) { delete location; location = CopyString(name); } if(status == 200 || (!status && totalSizeSet)) { if(askBody) this.connection.OnReceive = HTTPConnection::Read_OnReceive; result = true; connectionsMutex.Wait(); } else { if(askBody) { if(chunked) { //bool wait = false; this.connection.OnReceive = HTTPConnection::Read_OnReceive; while(!eof) { if(!this.connection) eof = true; else { // First time check if we already have bytes, second time wait for an event this.connection.Process(); //wait = true; } } } else if(totalSizeSet) { // Is it possible to have Content-Length set but not chunked? done = false; this.connection.OnReceive = HTTPConnection::Read_OnReceive; while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize) { connection.Process(); position += bufferCount - bufferPos; bufferCount = 0; bufferPos = 0; } } } connectionsMutex.Wait(); if(this.connection) { this.connection.OnReceive = null; this.connection.file = null; if(close) { this.connection.Disconnect(0); connection = null; } } status = 0; delete this.connection; // This decrements the file's reference this.relocation = null; totalSize = 0; totalSizeSet = false; done = false; eof = false; position = 0; bufferPos = 0; bufferCount = 0; chunked = false; } } else { connectionsMutex.Wait(); } if(reuse && !status && connection && !connection.connected) { delete connection; reuse = false; goto tryagain; } else delete connection; } connectionsMutex.Release(); } return result; } ~HTTPFile() { delete location; delete contentType; delete contentDisposition; { connectionsMutex.Wait(); if(connection) { if(totalSizeSet && askedBody) { done = false; this.connection.OnReceive = HTTPConnection::Read_OnReceive; while(this.connection && this.connection.connected && position + (bufferCount - bufferPos) < totalSize) { connectionsMutex.Release(); connection.Process(); connectionsMutex.Wait(); position += bufferCount - bufferPos; bufferCount = 0; bufferPos = 0; } position = 0; } if(connection) { connection.file = null; if(close) connection.Disconnect(0); delete connection; } } connectionsMutex.Release(); if(chunked) { while(connection && connection.connected && connection.file) { connectionsMutex.Release(); connection.Process(); connectionsMutex.Wait(); } } //::PrintLn("Done with ", (uint64)this); } } int Read(byte * buffer, uint size, uint count) { uint readSize = size * count; uint read = 0; bool wait = false; Time lastTime = GetTime(); if(!askedBody) { askedBody = true; if(!RetrieveHead(this.location, null, null, true)) return 0; } if(totalSizeSet && position >= totalSize) eof = true; while(!eof && read < readSize && !aborted) { uint numbytes = bufferCount - bufferPos; numbytes = Min(numbytes, readSize - read); if(totalSizeSet) numbytes = Min(numbytes, totalSize - position); if(numbytes) { lastTime = GetTime(); memcpy(buffer + read, this.buffer + bufferPos, numbytes); bufferPos += numbytes; position += numbytes; read += numbytes; lastTime = GetTime(); if(bufferPos > HTTPFILE_BUFFERSIZE / 2) { // Shift bytes back to beginning of buffer uint shift = bufferCount - bufferPos; if(shift) memmove(this.buffer, this.buffer + bufferPos, shift); bufferCount -= bufferPos; bufferPos = 0; } } else { if(!connection || !connection.connected) eof = true; else { // First time check if we already have bytes, second time wait for an event connection.Process(); if(wait && bufferCount - bufferPos == 0 && GetTime() - lastTime > 5) eof = true; wait = true; } } if(totalSizeSet && position >= totalSize) eof = true; } return read / size; } int Write(const byte * buffer, uint size, uint count) { return 0; } bool Getc(char * ch) { int read = Read(ch, 1, 1); return !eof && read != 0; } bool Putc(char ch) { return false; } bool Puts(const char * string) { return false; } bool Seek(int pos, FileSeekMode mode) { if(mode == start && bufferPos == 0 && pos <= bufferCount && pos >= 0) { bufferPos = pos; return true; } else if(mode == current && bufferPos == 0 && ((int)position + pos) <= bufferCount && ((int)position + pos) >= 0) { bufferPos = position + pos; return true; } else if(mode == end && totalSizeSet && bufferPos == 0 && bufferCount == totalSize && ((int)totalSize - pos) <= bufferCount && ((int)totalSize - pos) >= 0) { bufferPos = totalSize - pos; return true; } return false; } uint Tell() { return position; } bool Eof() { return eof; } uint GetSize() { return totalSize; } void Abort() { aborted = true; } private: bool askedBody; HTTPConnection connection; uint position; bool done; bool eof; int status; uint totalSize; bool chunked; bool close; uint chunkSize; char * relocation; String location; // Buffering... byte buffer[HTTPFILE_BUFFERSIZE]; uint bufferPos; uint bufferCount; bool aborted; bool totalSizeSet; String contentType; String contentDisposition; } public HTTPFile FileOpenURL(const char * name) { HTTPFile f { }; if(f.OpenURL(name, null, null)) return f; else { delete f; return null; } } #endif