Error HTTPClient::poll() { switch (status) { case STATUS_RESOLVING: { ERR_FAIL_COND_V(resolving == IP::RESOLVER_INVALID_ID, ERR_BUG); IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving); switch (rstatus) { case IP::RESOLVER_STATUS_WAITING: return OK; // Still resolving case IP::RESOLVER_STATUS_DONE: { IP_Address host = IP::get_singleton()->get_resolve_item_address(resolving); Error err = tcp_connection->connect_to_host(host, conn_port); IP::get_singleton()->erase_resolve_item(resolving); resolving = IP::RESOLVER_INVALID_ID; if (err) { status = STATUS_CANT_CONNECT; return err; } status = STATUS_CONNECTING; } break; case IP::RESOLVER_STATUS_NONE: case IP::RESOLVER_STATUS_ERROR: { IP::get_singleton()->erase_resolve_item(resolving); resolving = IP::RESOLVER_INVALID_ID; close(); status = STATUS_CANT_RESOLVE; return ERR_CANT_RESOLVE; } break; } } break; case STATUS_CONNECTING: { StreamPeerTCP::Status s = tcp_connection->get_status(); switch (s) { case StreamPeerTCP::STATUS_CONNECTING: { return OK; } break; case StreamPeerTCP::STATUS_CONNECTED: { if (ssl) { Ref<StreamPeerSSL> ssl; if (!handshaking) { // Connect the StreamPeerSSL and start handshaking ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); ssl->set_blocking_handshake_enabled(false); Error err = ssl->connect_to_stream(tcp_connection, ssl_verify_host, conn_host); if (err != OK) { close(); status = STATUS_SSL_HANDSHAKE_ERROR; return ERR_CANT_CONNECT; } connection = ssl; handshaking = true; } else { // We are already handshaking, which means we can use your already active SSL connection ssl = static_cast<Ref<StreamPeerSSL> >(connection); ssl->poll(); // Try to finish the handshake } if (ssl->get_status() == StreamPeerSSL::STATUS_CONNECTED) { // Handshake has been successfull handshaking = false; status = STATUS_CONNECTED; return OK; } else if (ssl->get_status() != StreamPeerSSL::STATUS_HANDSHAKING) { // Handshake has failed close(); status = STATUS_SSL_HANDSHAKE_ERROR; return ERR_CANT_CONNECT; } // ... we will need to poll more for handshake to finish } else { status = STATUS_CONNECTED; } return OK; } break; case StreamPeerTCP::STATUS_ERROR: case StreamPeerTCP::STATUS_NONE: { close(); status = STATUS_CANT_CONNECT; return ERR_CANT_CONNECT; } break; } } break; case STATUS_CONNECTED: { // Connection established, requests can now be made return OK; } break; case STATUS_REQUESTING: { while (true) { uint8_t byte; int rec = 0; Error err = _get_http_data(&byte, 1, rec); if (err != OK) { close(); status = STATUS_CONNECTION_ERROR; return ERR_CONNECTION_ERROR; } if (rec == 0) return OK; // Still requesting, keep trying! response_str.push_back(byte); int rs = response_str.size(); if ( (rs >= 2 && response_str[rs - 2] == '\n' && response_str[rs - 1] == '\n') || (rs >= 4 && response_str[rs - 4] == '\r' && response_str[rs - 3] == '\n' && response_str[rs - 2] == '\r' && response_str[rs - 1] == '\n')) { // End of response, parse. response_str.push_back(0); String response; response.parse_utf8((const char *)response_str.ptr()); Vector<String> responses = response.split("\n"); body_size = 0; chunked = false; body_left = 0; chunk_left = 0; read_until_eof = false; response_str.clear(); response_headers.clear(); response_num = RESPONSE_OK; // Per the HTTP 1.1 spec, keep-alive is the default, but in practice // it's safe to assume it only if the explicit header is found, allowing // to handle body-up-to-EOF responses on naive servers; that's what Curl // and browsers do bool keep_alive = false; for (int i = 0; i < responses.size(); i++) { String header = responses[i].strip_edges(); String s = header.to_lower(); if (s.length() == 0) continue; if (s.begins_with("content-length:")) { body_size = s.substr(s.find(":") + 1, s.length()).strip_edges().to_int(); body_left = body_size; } else if (s.begins_with("transfer-encoding:")) { String encoding = header.substr(header.find(":") + 1, header.length()).strip_edges(); if (encoding == "chunked") { chunked = true; } } else if (s.begins_with("connection: keep-alive")) { keep_alive = true; } if (i == 0 && responses[i].begins_with("HTTP")) { String num = responses[i].get_slicec(' ', 1); response_num = num.to_int(); } else { response_headers.push_back(header); } } if (body_size || chunked) { status = STATUS_BODY; } else if (!keep_alive) { read_until_eof = true; status = STATUS_BODY; } else { status = STATUS_CONNECTED; } return OK; } } // Wait for response return OK; } break; case STATUS_DISCONNECTED: { return ERR_UNCONFIGURED; } break; case STATUS_CONNECTION_ERROR: { return ERR_CONNECTION_ERROR; } break; case STATUS_CANT_CONNECT: { return ERR_CANT_CONNECT; } break; case STATUS_CANT_RESOLVE: { return ERR_CANT_RESOLVE; } break; } return OK; }
PoolByteArray HTTPClient::read_response_body_chunk() { ERR_FAIL_COND_V(status != STATUS_BODY, PoolByteArray()); Error err = OK; if (chunked) { while (true) { if (chunk_left == 0) { // Reading length uint8_t b; int rec = 0; err = _get_http_data(&b, 1, rec); if (rec == 0) break; chunk.push_back(b); if (chunk.size() > 32) { ERR_PRINT("HTTP Invalid chunk hex len"); status = STATUS_CONNECTION_ERROR; return PoolByteArray(); } if (chunk.size() > 2 && chunk[chunk.size() - 2] == '\r' && chunk[chunk.size() - 1] == '\n') { int len = 0; for (int i = 0; i < chunk.size() - 2; i++) { char c = chunk[i]; int v = 0; if (c >= '0' && c <= '9') v = c - '0'; else if (c >= 'a' && c <= 'f') v = c - 'a' + 10; else if (c >= 'A' && c <= 'F') v = c - 'A' + 10; else { ERR_PRINT("HTTP Chunk len not in hex!!"); status = STATUS_CONNECTION_ERROR; return PoolByteArray(); } len <<= 4; len |= v; if (len > (1 << 24)) { ERR_PRINT("HTTP Chunk too big!! >16mb"); status = STATUS_CONNECTION_ERROR; return PoolByteArray(); } } if (len == 0) { // End reached! status = STATUS_CONNECTED; chunk.clear(); return PoolByteArray(); } chunk_left = len + 2; chunk.resize(chunk_left); } } else { int rec = 0; err = _get_http_data(&chunk[chunk.size() - chunk_left], chunk_left, rec); if (rec == 0) { break; } chunk_left -= rec; if (chunk_left == 0) { if (chunk[chunk.size() - 2] != '\r' || chunk[chunk.size() - 1] != '\n') { ERR_PRINT("HTTP Invalid chunk terminator (not \\r\\n)"); status = STATUS_CONNECTION_ERROR; return PoolByteArray(); } PoolByteArray ret; ret.resize(chunk.size() - 2); { PoolByteArray::Write w = ret.write(); copymem(w.ptr(), chunk.ptr(), chunk.size() - 2); } chunk.clear(); return ret; } break; } } } else { int to_read = !read_until_eof ? MIN(body_left, read_chunk_size) : read_chunk_size; PoolByteArray ret; ret.resize(to_read); int _offset = 0; while (read_until_eof || to_read > 0) { int rec = 0; { PoolByteArray::Write w = ret.write(); err = _get_http_data(w.ptr() + _offset, to_read, rec); } if (rec < 0) { if (to_read > 0) // Ended up reading less ret.resize(_offset); break; } else { _offset += rec; if (!read_until_eof) { body_left -= rec; to_read -= rec; } else { if (rec < to_read) { ret.resize(_offset); err = ERR_FILE_EOF; break; } ret.resize(_offset + to_read); } } } if (!read_until_eof) { if (body_left == 0) { status = STATUS_CONNECTED; } return ret; } else { if (err == ERR_FILE_EOF) { err = OK; // EOF is expected here close(); return ret; } } } if (err != OK) { close(); if (err == ERR_FILE_EOF) { status = STATUS_DISCONNECTED; // Server disconnected } else { status = STATUS_CONNECTION_ERROR; } } else if (body_left == 0 && !chunked) { status = STATUS_CONNECTED; } return PoolByteArray(); }
Error HTTPClient::poll(){ switch(status) { case STATUS_RESOLVING: { ERR_FAIL_COND_V(resolving==IP::RESOLVER_INVALID_ID,ERR_BUG); IP::ResolverStatus rstatus = IP::get_singleton()->get_resolve_item_status(resolving); switch(rstatus) { case IP::RESOLVER_STATUS_WAITING: return OK; //still resolving case IP::RESOLVER_STATUS_DONE: { IP_Address host = IP::get_singleton()->get_resolve_item_address(resolving); Error err = tcp_connection->connect(host,conn_port); IP::get_singleton()->erase_resolve_item(resolving); resolving=IP::RESOLVER_INVALID_ID; if (err) { status=STATUS_CANT_CONNECT; return err; } status=STATUS_CONNECTING; } break; case IP::RESOLVER_STATUS_NONE: case IP::RESOLVER_STATUS_ERROR: { IP::get_singleton()->erase_resolve_item(resolving); resolving=IP::RESOLVER_INVALID_ID; close(); status=STATUS_CANT_RESOLVE; return ERR_CANT_RESOLVE; } break; } } break; case STATUS_CONNECTING: { StreamPeerTCP::Status s = tcp_connection->get_status(); switch(s) { case StreamPeerTCP::STATUS_CONNECTING: { return OK; //do none } break; case StreamPeerTCP::STATUS_CONNECTED: { if (ssl) { Ref<StreamPeerSSL> ssl = StreamPeerSSL::create(); Error err = ssl->connect(tcp_connection,true,ssl_verify_host?conn_host:String()); if (err!=OK) { close(); status=STATUS_SSL_HANDSHAKE_ERROR; return ERR_CANT_CONNECT; } print_line("SSL! TURNED ON!"); connection=ssl; } status=STATUS_CONNECTED; return OK; } break; case StreamPeerTCP::STATUS_ERROR: case StreamPeerTCP::STATUS_NONE: { close(); status=STATUS_CANT_CONNECT; return ERR_CANT_CONNECT; } break; } } break; case STATUS_CONNECTED: { //request something please return OK; } break; case STATUS_REQUESTING: { while(true) { uint8_t byte; int rec=0; Error err = _get_http_data(&byte,1,rec); if (err!=OK) { close(); status=STATUS_CONNECTION_ERROR; return ERR_CONNECTION_ERROR; } if (rec==0) return OK; //keep trying! response_str.push_back(byte); int rs = response_str.size(); if ( (rs>=2 && response_str[rs-2]=='\n' && response_str[rs-1]=='\n') || (rs>=4 && response_str[rs-4]=='\r' && response_str[rs-3]=='\n' && rs>=4 && response_str[rs-2]=='\r' && response_str[rs-1]=='\n') ) { //end of response, parse. response_str.push_back(0); String response; response.parse_utf8((const char*)response_str.ptr()); print_line("END OF RESPONSE? :\n"+response+"\n------"); Vector<String> responses = response.split("\n"); body_size=0; chunked=false; body_left=0; chunk_left=0; response_headers.clear(); response_num = RESPONSE_OK; for(int i=0;i<responses.size();i++) { String s = responses[i].strip_edges(); if (s.length()==0) continue; if (s.begins_with("Content-Length:")) { body_size = s.substr(s.find(":")+1,s.length()).strip_edges().to_int(); body_left=body_size; } if (s.begins_with("Transfer-Encoding:")) { String encoding = s.substr(s.find(":")+1,s.length()).strip_edges(); print_line("TRANSFER ENCODING: "+encoding); if (encoding=="chunked") { chunked=true; } } if (i==0 && responses[i].begins_with("HTTP")) { String num = responses[i].get_slicec(' ',1); response_num=num.to_int(); } else { response_headers.push_back(s); } } if (body_size==0 && !chunked) { status=STATUS_CONNECTED; //ask for something again? } else { status=STATUS_BODY; } return OK; } } //wait for response return OK; } break; case STATUS_DISCONNECTED: { return ERR_UNCONFIGURED; } break; case STATUS_CONNECTION_ERROR: { return ERR_CONNECTION_ERROR; } break; case STATUS_CANT_CONNECT: { return ERR_CANT_CONNECT; } break; case STATUS_CANT_RESOLVE: { return ERR_CANT_RESOLVE; } break; } return OK; }