status_t BHttpRequest::_ProtocolLoop() { // Initialize the request redirection loop int8 maxRedirs = fOptMaxRedirs; bool newRequest; do { newRequest = false; // Result reset fOutputHeaders.Clear(); fHeaders.Clear(); _ResultHeaders().Clear(); if (!_ResolveHostName()) { _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Unable to resolve hostname (%s), aborting.", fUrl.Host().String()); return B_SERVER_NOT_FOUND; } status_t requestStatus = _MakeRequest(); if (requestStatus != B_OK) return requestStatus; // Prepare the referer for the next request if needed if (fOptAutoReferer) fOptReferer = fUrl.UrlString(); switch (StatusCodeClass(fResult.StatusCode())) { case B_HTTP_STATUS_CLASS_INFORMATIONAL: // Header 100:continue should have been // handled in the _MakeRequest read loop break; case B_HTTP_STATUS_CLASS_SUCCESS: break; case B_HTTP_STATUS_CLASS_REDIRECTION: // Redirection has been explicitly disabled if (!fOptFollowLocation) break; // TODO: Some browsers seems to translate POST requests to // GET when following a 302 redirection if (fResult.StatusCode() == B_HTTP_STATUS_MOVED_PERMANENTLY) { BString locationUrl = fHeaders["Location"]; fUrl = BUrl(fUrl, locationUrl); if (--maxRedirs > 0) { newRequest = true; _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Following: %s\n", fUrl.UrlString().String()); } } break; case B_HTTP_STATUS_CLASS_CLIENT_ERROR: if (fResult.StatusCode() == B_HTTP_STATUS_UNAUTHORIZED) { BHttpAuthentication* authentication = &fContext->GetAuthentication(fUrl); status_t status = B_OK; if (authentication->Method() == B_HTTP_AUTHENTICATION_NONE) { // There is no authentication context for this // url yet, so let's create one. authentication = new(std::nothrow) BHttpAuthentication(); if (authentication == NULL) status = B_NO_MEMORY; else { status = authentication->Initialize( fHeaders["WWW-Authenticate"]); fContext->AddAuthentication(fUrl, authentication); } } newRequest = false; if (fOptUsername.Length() > 0 && status == B_OK) { // If we received an username and password, add them // to the request. This will either change the // credentials for an existing request, or set them // for a new one we created just above. // // If this request handles HTTP redirections, it will // also automatically retry connecting and send the // login information. authentication->SetUserName(fOptUsername); authentication->SetPassword(fOptPassword); newRequest = true; } } break; case B_HTTP_STATUS_CLASS_SERVER_ERROR: break; default: case B_HTTP_STATUS_CLASS_INVALID: break; } } while (newRequest); _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "%ld headers and %ld bytes of data remaining", fHeaders.CountHeaders(), fInputBuffer.Size()); if (fResult.StatusCode() == 404) return B_RESOURCE_NOT_FOUND; return B_OK; }
status_t BHttpRequest::_ProtocolLoop() { // Initialize the request redirection loop int8 maxRedirs = fOptMaxRedirs; bool newRequest; do { newRequest = false; // Result reset fHeaders.Clear(); _ResultHeaders().Clear(); BString host = fUrl.Host(); int port = fSSL ? 443 : 80; if (fUrl.HasPort()) port = fUrl.Port(); if (fContext->UseProxy()) { host = fContext->GetProxyHost(); port = fContext->GetProxyPort(); } status_t result = fInputBuffer.InitCheck(); if (result != B_OK) return result; if (!_ResolveHostName(host, port)) { _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Unable to resolve hostname (%s), aborting.", fUrl.Host().String()); return B_SERVER_NOT_FOUND; } status_t requestStatus = _MakeRequest(); if (requestStatus != B_OK) return requestStatus; // Prepare the referer for the next request if needed if (fOptAutoReferer) fOptReferer = fUrl.UrlString(); switch (StatusCodeClass(fResult.StatusCode())) { case B_HTTP_STATUS_CLASS_INFORMATIONAL: // Header 100:continue should have been // handled in the _MakeRequest read loop break; case B_HTTP_STATUS_CLASS_SUCCESS: break; case B_HTTP_STATUS_CLASS_REDIRECTION: { // Redirection has been explicitly disabled if (!fOptFollowLocation) break; int code = fResult.StatusCode(); if (code == B_HTTP_STATUS_MOVED_PERMANENTLY || code == B_HTTP_STATUS_FOUND || code == B_HTTP_STATUS_SEE_OTHER || code == B_HTTP_STATUS_TEMPORARY_REDIRECT) { BString locationUrl = fHeaders["Location"]; fUrl = BUrl(fUrl, locationUrl); // 302 and 303 redirections also convert POST requests to GET // (and remove the posted form data) if ((code == B_HTTP_STATUS_FOUND || code == B_HTTP_STATUS_SEE_OTHER) && fRequestMethod == B_HTTP_POST) { SetMethod(B_HTTP_GET); delete fOptPostFields; fOptPostFields = NULL; delete fOptInputData; fOptInputData = NULL; fOptInputDataSize = 0; } if (--maxRedirs > 0) { newRequest = true; // Redirections may need a switch from http to https. if (fUrl.Protocol() == "https") fSSL = true; else if (fUrl.Protocol() == "http") fSSL = false; _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Following: %s\n", fUrl.UrlString().String()); } } break; } case B_HTTP_STATUS_CLASS_CLIENT_ERROR: if (fResult.StatusCode() == B_HTTP_STATUS_UNAUTHORIZED) { BHttpAuthentication* authentication = &fContext->GetAuthentication(fUrl); status_t status = B_OK; if (authentication->Method() == B_HTTP_AUTHENTICATION_NONE) { // There is no authentication context for this // url yet, so let's create one. BHttpAuthentication newAuth; newAuth.Initialize(fHeaders["WWW-Authenticate"]); fContext->AddAuthentication(fUrl, newAuth); // Get the copy of the authentication we just added. // That copy is owned by the BUrlContext and won't be // deleted (unlike the temporary object above) authentication = &fContext->GetAuthentication(fUrl); } newRequest = false; if (fOptUsername.Length() > 0 && status == B_OK) { // If we received an username and password, add them // to the request. This will either change the // credentials for an existing request, or set them // for a new one we created just above. // // If this request handles HTTP redirections, it will // also automatically retry connecting and send the // login information. authentication->SetUserName(fOptUsername); authentication->SetPassword(fOptPassword); newRequest = true; } } break; case B_HTTP_STATUS_CLASS_SERVER_ERROR: break; default: case B_HTTP_STATUS_CLASS_INVALID: break; } } while (newRequest); _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "%ld headers and %ld bytes of data remaining", fHeaders.CountHeaders(), fInputBuffer.Size()); if (fResult.StatusCode() == 404) return B_RESOURCE_NOT_FOUND; return B_OK; }
status_t BGopherRequest::_ProtocolLoop() { if (fSocket == NULL) return B_NO_MEMORY; if (!_ResolveHostName(fUrl.Host(), fUrl.HasPort() ? fUrl.Port() : 70)) { _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Unable to resolve hostname (%s), aborting.", fUrl.Host().String()); return B_SERVER_NOT_FOUND; } _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s on port %d.", fUrl.Authority().String(), fRemoteAddr.Port()); status_t connectError = fSocket->Connect(fRemoteAddr); if (connectError != B_OK) { _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Socket connection error %s", strerror(connectError)); return connectError; } //! ProtocolHook:ConnectionOpened if (fListener != NULL) fListener->ConnectionOpened(this); _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection opened, sending request."); _SendRequest(); _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent."); // Receive loop bool receiveEnd = false; status_t readError = B_OK; ssize_t bytesRead = 0; //ssize_t bytesReceived = 0; //ssize_t bytesTotal = 0; bool dataValidated = false; BStackOrHeapArray<char, 4096> chunk(kGopherBufferSize); while (!fQuit && !receiveEnd) { fSocket->WaitForReadable(); bytesRead = fSocket->Read(chunk, kGopherBufferSize); if (bytesRead < 0) { readError = bytesRead; break; } else if (bytesRead == 0) receiveEnd = true; fInputBuffer.AppendData(chunk, bytesRead); if (!dataValidated) { size_t i; // on error (file doesn't exist, ...) the server sends // a faked directory entry with an error message if (fInputBuffer.Size() && fInputBuffer.Data()[0] == '3') { int tabs = 0; bool crlf = false; // make sure the buffer only contains printable characters // and has at least 3 tabs before a CRLF for (i = 0; i < fInputBuffer.Size(); i++) { char c = fInputBuffer.Data()[i]; if (c == '\t') { if (!crlf) tabs++; } else if (c == '\r' || c == '\n') { if (tabs < 3) break; crlf = true; } else if (!isprint(fInputBuffer.Data()[i])) { crlf = false; break; } } if (crlf && tabs > 2 && tabs < 5) { // TODO: //if enough data // else continue fItemType = GOPHER_TYPE_DIRECTORY; readError = B_RESOURCE_NOT_FOUND; // continue parsing the error text anyway } } // special case for buggy(?) Gophernicus/1.5 static const char *buggy = "Error: File or directory not found!"; if (fInputBuffer.Size() > strlen(buggy) && !memcmp(fInputBuffer.Data(), buggy, strlen(buggy))) { fItemType = GOPHER_TYPE_DIRECTORY; readError = B_RESOURCE_NOT_FOUND; // continue parsing the error text anyway // but it won't look good } // now we probably have correct data dataValidated = true; //! ProtocolHook:ResponseStarted if (fListener != NULL) fListener->ResponseStarted(this); // we don't really have headers but well... //! ProtocolHook:HeadersReceived if (fListener != NULL) fListener->HeadersReceived(this); // now we can assign MIME type if we know it const char *mime = "application/octet-stream"; for (i = 0; gopher_type_map[i].type != GOPHER_TYPE_NONE; i++) { if (gopher_type_map[i].type == fItemType) { mime = gopher_type_map[i].mime; break; } } fResult.SetContentType(mime); } if (_NeedsParsing()) _ParseInput(receiveEnd); else if (fInputBuffer.Size()) { // send input directly if (fListener != NULL) { fListener->DataReceived(this, (const char *)fInputBuffer.Data(), fPosition, fInputBuffer.Size()); } fPosition += fInputBuffer.Size(); // XXX: this is plain stupid, we already copied the data // and just want to drop it... char *inputTempBuffer = new(std::nothrow) char[bytesRead]; if (inputTempBuffer == NULL) { readError = B_NO_MEMORY; break; } fInputBuffer.RemoveData(inputTempBuffer, fInputBuffer.Size()); delete[] inputTempBuffer; } } if (fPosition > 0) { fResult.SetLength(fPosition); if (fListener != NULL) fListener->DownloadProgress(this, fPosition, fPosition); } fSocket->Disconnect(); if (readError != B_OK) return readError; return fQuit ? B_INTERRUPTED : B_OK; }