status_t BUrlProtocolHttp::_MakeRequest() { _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection to %s.", fUrl.Authority().String()); status_t connectError = fSocket.Connect(fRemoteAddr); if (connectError != B_OK) { _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Connection error: %s.", fSocket.ErrorStr()); return B_PROT_CONNECTION_FAILED; } //! ProtocolHook:ConnectionOpened if (fListener != NULL) fListener->ConnectionOpened(this); _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Connection opened."); _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Sending request (size=%d)", fOutputBuffer.Length()); fSocket.Send(fOutputBuffer.String(), fOutputBuffer.Length()); fOutputBuffer.Truncate(0); _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent."); if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) { if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) { fOutputBuffer = fOptPostFields->RawData(); _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT, fOutputBuffer.String()); fSocket.Send(fOutputBuffer.String(), fOutputBuffer.Length()); } else { for (BHttpForm::Iterator it = fOptPostFields->GetIterator(); const BHttpFormData* currentField = it.Next(); ) { _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT, it.MultipartHeader().String()); fSocket.Send(it.MultipartHeader().String(), it.MultipartHeader().Length()); switch (currentField->Type()) { case B_HTTPFORM_UNKNOWN: ASSERT(0); break; case B_HTTPFORM_STRING: fSocket.Send(currentField->String().String(), currentField->String().Length()); break; case B_HTTPFORM_FILE: { BFile upFile(currentField->File().Path(), B_READ_ONLY); char readBuffer[1024]; ssize_t readSize; readSize = upFile.Read(readBuffer, 1024); while (readSize > 0) { fSocket.Send(readBuffer, readSize); readSize = upFile.Read(readBuffer, 1024); } } break; case B_HTTPFORM_BUFFER: fSocket.Send(currentField->Buffer(), currentField->BufferSize()); break; } fSocket.Send("\r\n", 2); } fSocket.Send(fOptPostFields->GetMultipartFooter().String(), fOptPostFields->GetMultipartFooter().Length()); } } else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT) && fOptInputData != NULL) { char outputTempBuffer[1024]; ssize_t read = 0; while (read != -1) { read = fOptInputData->Read(outputTempBuffer, 1024); if (read > 0) { char hexSize[16]; size_t hexLength = sprintf(hexSize, "%ld", read); fSocket.Send(hexSize, hexLength); fSocket.Send("\r\n", 2); fSocket.Send(outputTempBuffer, read); fSocket.Send("\r\n", 2); } } fSocket.Send("0\r\n\r\n", 5); } fOutputBuffer.Truncate(0, true); fSocket.SetNonBlocking(false); fStatusReceived = false; fHeadersReceived = false; // Receive loop bool receiveEnd = false; bool parseEnd = false; bool readByChunks = false; bool readError = false; int32 receiveBufferSize = 32; ssize_t bytesRead = 0; ssize_t bytesReceived = 0; ssize_t bytesTotal = 0; char* inputTempBuffer = NULL; fQuit = false; while (!fQuit && !(receiveEnd && parseEnd)) { if (!receiveEnd) { bytesRead = fSocket.Receive(fInputBuffer, receiveBufferSize); if (bytesRead < 0) { readError = true; fQuit = true; continue; } else if (bytesRead == 0) receiveEnd = true; } else bytesRead = 0; if (!fStatusReceived) { _ParseStatus(); //! ProtocolHook:ResponseStarted if (fStatusReceived && fListener != NULL) fListener->ResponseStarted(this); } else if (!fHeadersReceived) { _ParseHeaders(); if (fHeadersReceived) { receiveBufferSize = kHttpProtocolReceiveBufferSize; _ResultHeaders() = fHeaders; //! ProtocolHook:HeadersReceived if (fListener != NULL) fListener->HeadersReceived(this); // Parse received cookies if ((fContext != NULL) && fHeaders.HasHeader("Set-Cookie")) { for (int32 i = 0; i < fHeaders.CountHeaders(); i++) { if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) { } } } if (BString(fHeaders["Transfer-Encoding"]) == "chunked") readByChunks = true; int32 index = fHeaders.HasHeader("Content-Length"); if (index != B_ERROR) bytesTotal = atoi(fHeaders.HeaderAt(index).Value()); else bytesTotal = 0; } } else { // If Transfer-Encoding is chunked, we should read a complete // chunk in buffer before handling it if (readByChunks) { _CopyChunkInBuffer(&inputTempBuffer, &bytesRead); // A chunk of 0 bytes indicates the end of the chunked transfer if (bytesRead == 0) { receiveEnd = true; } } else { bytesRead = fInputBuffer.Size(); if (bytesRead > 0) { inputTempBuffer = new char[bytesRead]; fInputBuffer.RemoveData(inputTempBuffer, bytesRead); } } if (bytesRead > 0) { bytesReceived += bytesRead; _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_IN, "%d bytes", bytesRead); if (fListener != NULL) { fListener->DataReceived(this, inputTempBuffer, bytesRead); fListener->DownloadProgress(this, bytesReceived, bytesTotal); } ssize_t dataWrite = _ResultRawData().Write(inputTempBuffer, bytesRead); if (dataWrite != bytesRead) { _EmitDebug(B_URL_PROTOCOL_DEBUG_ERROR, "Unable to write %dbytes of data (%d).", bytesRead, dataWrite); return B_PROT_NO_MEMORY; } if (bytesTotal > 0 && bytesReceived >= bytesTotal) receiveEnd = true; delete[] inputTempBuffer; } } parseEnd = (fInputBuffer.Size() == 0); } fSocket.Close(); if (readError) return B_PROT_READ_FAILED; return fQuit?B_PROT_ABORTED:B_PROT_SUCCESS; }
void BHttpRequest::_SendPostData() { if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) { if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) { BString outputBuffer = fOptPostFields->RawData(); _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT, "%s", outputBuffer.String()); fSocket->Write(outputBuffer.String(), outputBuffer.Length()); } else { for (BHttpForm::Iterator it = fOptPostFields->GetIterator(); const BHttpFormData* currentField = it.Next(); ) { _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT, it.MultipartHeader().String()); fSocket->Write(it.MultipartHeader().String(), it.MultipartHeader().Length()); switch (currentField->Type()) { default: case B_HTTPFORM_UNKNOWN: ASSERT(0); break; case B_HTTPFORM_STRING: fSocket->Write(currentField->String().String(), currentField->String().Length()); break; case B_HTTPFORM_FILE: { BFile upFile(currentField->File().Path(), B_READ_ONLY); char readBuffer[kHttpBufferSize]; ssize_t readSize; readSize = upFile.Read(readBuffer, sizeof(readBuffer)); while (readSize > 0) { fSocket->Write(readBuffer, readSize); readSize = upFile.Read(readBuffer, sizeof(readBuffer)); } break; } case B_HTTPFORM_BUFFER: fSocket->Write(currentField->Buffer(), currentField->BufferSize()); break; } fSocket->Write("\r\n", 2); } BString footer = fOptPostFields->GetMultipartFooter(); fSocket->Write(footer.String(), footer.Length()); } } else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT) && fOptInputData != NULL) { // If the input data is seekable, we rewind it for each new request. BPositionIO* seekableData = dynamic_cast<BPositionIO*>(fOptInputData); if (seekableData) seekableData->Seek(0, SEEK_SET); for (;;) { char outputTempBuffer[kHttpBufferSize]; ssize_t read = fOptInputData->Read(outputTempBuffer, sizeof(outputTempBuffer)); if (read <= 0) break; if (fOptInputDataSize < 0) { // Chunked transfer char hexSize[16]; size_t hexLength = sprintf(hexSize, "%ld", read); fSocket->Write(hexSize, hexLength); fSocket->Write("\r\n", 2); fSocket->Write(outputTempBuffer, read); fSocket->Write("\r\n", 2); } else { fSocket->Write(outputTempBuffer, read); } } if (fOptInputDataSize < 0) { // Chunked transfer terminating sequence fSocket->Write("0\r\n\r\n", 5); } } }
status_t BHttpRequest::_MakeRequest() { if (fSocket == NULL) return B_NO_MEMORY; _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(); _SendHeaders(); fSocket->Write("\r\n", 2); _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Request sent."); if (fRequestMethod == B_HTTP_POST && fOptPostFields != NULL) { if (fOptPostFields->GetFormType() != B_HTTP_FORM_MULTIPART) { BString outputBuffer = fOptPostFields->RawData(); _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT, "%s", outputBuffer.String()); fSocket->Write(outputBuffer.String(), outputBuffer.Length()); } else { for (BHttpForm::Iterator it = fOptPostFields->GetIterator(); const BHttpFormData* currentField = it.Next(); ) { _EmitDebug(B_URL_PROTOCOL_DEBUG_TRANSFER_OUT, it.MultipartHeader().String()); fSocket->Write(it.MultipartHeader().String(), it.MultipartHeader().Length()); switch (currentField->Type()) { default: case B_HTTPFORM_UNKNOWN: ASSERT(0); break; case B_HTTPFORM_STRING: fSocket->Write(currentField->String().String(), currentField->String().Length()); break; case B_HTTPFORM_FILE: { BFile upFile(currentField->File().Path(), B_READ_ONLY); char readBuffer[kHttpBufferSize]; ssize_t readSize; readSize = upFile.Read(readBuffer, sizeof(readBuffer)); while (readSize > 0) { fSocket->Write(readBuffer, readSize); readSize = upFile.Read(readBuffer, sizeof(readBuffer)); } break; } case B_HTTPFORM_BUFFER: fSocket->Write(currentField->Buffer(), currentField->BufferSize()); break; } fSocket->Write("\r\n", 2); } BString footer = fOptPostFields->GetMultipartFooter(); fSocket->Write(footer.String(), footer.Length()); } } else if ((fRequestMethod == B_HTTP_POST || fRequestMethod == B_HTTP_PUT) && fOptInputData != NULL) { for (;;) { char outputTempBuffer[kHttpBufferSize]; ssize_t read = fOptInputData->Read(outputTempBuffer, sizeof(outputTempBuffer)); if (read <= 0) break; if (fOptInputDataSize < 0) { // Chunked transfer char hexSize[16]; size_t hexLength = sprintf(hexSize, "%ld", read); fSocket->Write(hexSize, hexLength); fSocket->Write("\r\n", 2); fSocket->Write(outputTempBuffer, read); fSocket->Write("\r\n", 2); } else { fSocket->Write(outputTempBuffer, read); } } if (fOptInputDataSize < 0) { // Chunked transfer terminating sequence fSocket->Write("0\r\n\r\n", 5); } } fRequestStatus = kRequestInitialState; // Receive loop bool receiveEnd = false; bool parseEnd = false; bool readByChunks = false; bool decompress = false; status_t readError = B_OK; ssize_t bytesRead = 0; ssize_t bytesReceived = 0; ssize_t bytesTotal = 0; char* inputTempBuffer = new(std::nothrow) char[kHttpBufferSize]; ssize_t inputTempSize = kHttpBufferSize; ssize_t chunkSize = -1; DynamicBuffer decompressorStorage; BPrivate::ZlibDecompressor decompressor(&decompressorStorage); while (!fQuit && !(receiveEnd && parseEnd)) { if (!receiveEnd) { fSocket->WaitForReadable(); BNetBuffer chunk(kHttpBufferSize); bytesRead = fSocket->Read(chunk.Data(), kHttpBufferSize); if (bytesRead < 0) { readError = bytesRead; break; } else if (bytesRead == 0) receiveEnd = true; fInputBuffer.AppendData(chunk.Data(), bytesRead); } else bytesRead = 0; if (fRequestStatus < kRequestStatusReceived) { _ParseStatus(); //! ProtocolHook:ResponseStarted if (fRequestStatus >= kRequestStatusReceived && fListener != NULL) fListener->ResponseStarted(this); } if (fRequestStatus < kRequestHeadersReceived) { _ParseHeaders(); if (fRequestStatus >= kRequestHeadersReceived) { _ResultHeaders() = fHeaders; //! ProtocolHook:HeadersReceived if (fListener != NULL) fListener->HeadersReceived(this); // Parse received cookies if (fContext != NULL) { for (int32 i = 0; i < fHeaders.CountHeaders(); i++) { if (fHeaders.HeaderAt(i).NameIs("Set-Cookie")) { fContext->GetCookieJar().AddCookie( fHeaders.HeaderAt(i).Value(), fUrl); } } } if (BString(fHeaders["Transfer-Encoding"]) == "chunked") readByChunks = true; BString contentEncoding(fHeaders["Content-Encoding"]); if (contentEncoding == "gzip" || contentEncoding == "deflate") { decompress = true; decompressor.Init(); } int32 index = fHeaders.HasHeader("Content-Length"); if (index != B_ERROR) bytesTotal = atoi(fHeaders.HeaderAt(index).Value()); else bytesTotal = 0; } } if (fRequestStatus >= kRequestHeadersReceived) { // If Transfer-Encoding is chunked, we should read a complete // chunk in buffer before handling it if (readByChunks) { if (chunkSize >= 0) { if ((ssize_t)fInputBuffer.Size() >= chunkSize + 2) { // 2 more bytes to handle the closing CR+LF bytesRead = chunkSize; if (inputTempSize < chunkSize + 2) { delete[] inputTempBuffer; inputTempSize = chunkSize + 2; inputTempBuffer = new(std::nothrow) char[inputTempSize]; } if (inputTempBuffer == NULL) { readError = B_NO_MEMORY; break; } fInputBuffer.RemoveData(inputTempBuffer, chunkSize + 2); chunkSize = -1; } else { // Not enough data, try again later bytesRead = -1; } } else { BString chunkHeader; if (_GetLine(chunkHeader) == B_ERROR) { chunkSize = -1; bytesRead = -1; } else { // Format of a chunk header: // <chunk size in hex>[; optional data] int32 semiColonIndex = chunkHeader.FindFirst(';', 0); // Cut-off optional data if present if (semiColonIndex != -1) { chunkHeader.Remove(semiColonIndex, chunkHeader.Length() - semiColonIndex); } chunkSize = strtol(chunkHeader.String(), NULL, 16); PRINT(("BHP[%p] Chunk %s=%ld\n", this, chunkHeader.String(), chunkSize)); if (chunkSize == 0) fRequestStatus = kRequestContentReceived; bytesRead = -1; } } // A chunk of 0 bytes indicates the end of the chunked transfer if (bytesRead == 0) receiveEnd = true; } else { bytesRead = fInputBuffer.Size(); if (bytesRead > 0) { if (inputTempSize < bytesRead) { inputTempSize = bytesRead; delete[] inputTempBuffer; inputTempBuffer = new(std::nothrow) char[bytesRead]; } if (inputTempBuffer == NULL) { readError = B_NO_MEMORY; break; } fInputBuffer.RemoveData(inputTempBuffer, bytesRead); } } if (bytesRead > 0) { bytesReceived += bytesRead; if (fListener != NULL) { if (decompress) { decompressor.DecompressNext(inputTempBuffer, bytesRead); ssize_t size = decompressorStorage.Size(); BStackOrHeapArray<char, 4096> buffer(size); size = decompressorStorage.Read(buffer, size); if (size > 0) { fListener->DataReceived(this, buffer, size); } } else { fListener->DataReceived(this, inputTempBuffer, bytesRead); } fListener->DownloadProgress(this, bytesReceived, bytesTotal); } if (bytesTotal > 0 && bytesReceived >= bytesTotal) { receiveEnd = true; if (decompress) { decompressor.Finish(); ssize_t size = decompressorStorage.Size(); BStackOrHeapArray<char, 4096> buffer(size); size = decompressorStorage.Read(buffer, size); if (fListener != NULL && size > 0) { fListener->DataReceived(this, buffer, size); } } } } } parseEnd = (fInputBuffer.Size() == 0); } fSocket->Disconnect(); delete[] inputTempBuffer; if (readError != B_OK) return readError; return fQuit ? B_INTERRUPTED : B_OK; }