Пример #1
0
bool
BHttpRequest::_ResolveHostName()
{
	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Resolving %s",
		fUrl.UrlString().String());

	uint16_t port;
	if (fUrl.HasPort())
		port = fUrl.Port();
	else
		port = fSSL ? 443 : 80;

	// FIXME stop forcing AF_INET, when BNetworkAddress stops giving IPv6
	// addresses when there isn't an IPv6 link available.
	fRemoteAddr = BNetworkAddress(AF_INET, fUrl.Host(), port);

	if (fRemoteAddr.InitCheck() != B_OK)
		return false;

	//! ProtocolHook:HostnameResolved
	if (fListener != NULL)
		fListener->HostnameResolved(this, fRemoteAddr.ToString().String());

	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Hostname resolved to: %s",
		fRemoteAddr.ToString().String());

	return true;
}
Пример #2
0
void
BHttpRequest::_ParseStatus()
{
	// Status line should be formatted like: HTTP/M.m SSS ...
	// With:   M = Major version of the protocol
	//         m = Minor version of the protocol
	//       SSS = three-digit status code of the response
	//       ... = additional text info
	BString statusLine;
	if (_GetLine(statusLine) == B_ERROR)
		return;

	if (statusLine.CountChars() < 12)
		return;

	fRequestStatus = kRequestStatusReceived;

	BString statusCodeStr;
	BString statusText;
	statusLine.CopyInto(statusCodeStr, 9, 3);
	_SetResultStatusCode(atoi(statusCodeStr.String()));

	statusLine.CopyInto(_ResultStatusText(), 13, statusLine.Length() - 13);

	_EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT, "Status line received: Code %d (%s)",
		atoi(statusCodeStr.String()), _ResultStatusText().String());
}
Пример #3
0
void
BHttpRequest::_ParseHeaders()
{
	BString currentHeader;
	while (_GetLine(currentHeader) != B_ERROR) {
		// An empty line means the end of the header section
		if (currentHeader.Length() == 0) {
			fRequestStatus = kRequestHeadersReceived;
			return;
		}

		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_IN, "%s",
			currentHeader.String());
		fHeaders.AddHeader(currentHeader.String());
	}
}
Пример #4
0
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);
		}
	}

}
Пример #5
0
void
BHttpRequest::_SendHeaders()
{
	BHttpHeaders outputHeaders;

	// HTTP 1.1 additional headers
	if (fHttpVersion == B_HTTP_11) {
		BString host = Url().Host();
		if (Url().HasPort() && !_IsDefaultPort())
			host << ':' << Url().Port();

		outputHeaders.AddHeader("Host", host);

		outputHeaders.AddHeader("Accept", "*/*");
		outputHeaders.AddHeader("Accept-Encoding", "gzip");
			// Allows the server to compress data using the "gzip" format.
			// "deflate" is not supported, because there are two interpretations
			// of what it means (the RFC and Microsoft products), and we don't
			// want to handle this. Very few websites support only deflate,
			// and most of them will send gzip, or at worst, uncompressed data.

		outputHeaders.AddHeader("Connection", "close");
			// Let the remote server close the connection after response since
			// we don't handle multiple request on a single connection
	}

	// Classic HTTP headers
	if (fOptUserAgent.CountChars() > 0)
		outputHeaders.AddHeader("User-Agent", fOptUserAgent.String());

	if (fOptReferer.CountChars() > 0)
		outputHeaders.AddHeader("Referer", fOptReferer.String());

	// Authentication
	if (fContext != NULL) {
		BHttpAuthentication& authentication = fContext->GetAuthentication(fUrl);
		if (authentication.Method() != B_HTTP_AUTHENTICATION_NONE) {
			if (fOptUsername.Length() > 0) {
				authentication.SetUserName(fOptUsername);
				authentication.SetPassword(fOptPassword);
			}

			BString request(fRequestMethod);
			outputHeaders.AddHeader("Authorization",
				authentication.Authorization(fUrl, request));
		}
	}

	// Required headers for POST data
	if (fOptPostFields != NULL && fRequestMethod == B_HTTP_POST) {
		BString contentType;

		switch (fOptPostFields->GetFormType()) {
			case B_HTTP_FORM_MULTIPART:
				contentType << "multipart/form-data; boundary="
					<< fOptPostFields->GetMultipartBoundary() << "";
				break;

			case B_HTTP_FORM_URL_ENCODED:
				contentType << "application/x-www-form-urlencoded";
				break;
		}

		outputHeaders.AddHeader("Content-Type", contentType);
		outputHeaders.AddHeader("Content-Length",
			fOptPostFields->ContentLength());
	} else if (fOptInputData != NULL
			&& (fRequestMethod == B_HTTP_POST
			|| fRequestMethod == B_HTTP_PUT)) {
		if (fOptInputDataSize >= 0)
			outputHeaders.AddHeader("Content-Length", fOptInputDataSize);
		else
			outputHeaders.AddHeader("Transfer-Encoding", "chunked");
	}

	// Optional headers specified by the user
	if (fOptHeaders != NULL) {
		for (int32 headerIndex = 0; headerIndex < fOptHeaders->CountHeaders();
				headerIndex++) {
			BHttpHeader& optHeader = (*fOptHeaders)[headerIndex];
			int32 replaceIndex = outputHeaders.HasHeader(optHeader.Name());

			// Add or replace the current option header to the
			// output header list
			if (replaceIndex == -1)
				outputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
			else
				outputHeaders[replaceIndex].SetValue(optHeader.Value());
		}
	}

	// Context cookies
	if (fOptSetCookies && fContext != NULL) {
		BString cookieString;

		BNetworkCookieJar::UrlIterator iterator
			= fContext->GetCookieJar().GetUrlIterator(fUrl);
		const BNetworkCookie* cookie = iterator.Next();
		if (cookie != NULL) {
			while (true) {
				cookieString << cookie->RawCookie(false);
				cookie = iterator.Next();
				if (cookie == NULL)
					break;
				cookieString << "; ";
			}

			outputHeaders.AddHeader("Cookie", cookieString);
		}
	}

	// Write output headers to output stream
	BString headerData;

	for (int32 headerIndex = 0; headerIndex < outputHeaders.CountHeaders();
			headerIndex++) {
		const char* header = outputHeaders.HeaderAt(headerIndex).Header();

		headerData << header;
		headerData << "\r\n";

		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", header);
	}

	fSocket->Write(headerData.String(), headerData.Length());
}
Пример #6
0
status_t
BHttpRequest::_MakeRequest()
{
	delete fSocket;

	if (fSSL)
		fSocket = new(std::nothrow) CheckedSecureSocket(this);
	else
		fSocket = new(std::nothrow) BSocket();

	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.");

	_SendPostData();
	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;
	off_t bytesUnpacked = 0;
	char* inputTempBuffer = new(std::nothrow) char[kHttpBufferSize];
	ssize_t inputTempSize = kHttpBufferSize;
	ssize_t chunkSize = -1;
	DynamicBuffer decompressorStorage;
	BDataIO* decompressingStream;
	ObjectDeleter<BDataIO> decompressingStreamDeleter;

	while (!fQuit && !(receiveEnd && parseEnd)) {
		if (!receiveEnd) {
			fSocket->WaitForReadable();
			BStackOrHeapArray<char, 4096> chunk(kHttpBufferSize);
			bytesRead = fSocket->Read(chunk, kHttpBufferSize);

			if (bytesRead < 0) {
				readError = bytesRead;
				break;
			} else if (bytesRead == 0)
				receiveEnd = true;

			fInputBuffer.AppendData(chunk, 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"]);
				// We don't advertise "deflate" support (see above), but we
				// still try to decompress it, if a server ever sends a deflate
				// stream despite it not being in our Accept-Encoding list.
				if (contentEncoding == "gzip"
						|| contentEncoding == "deflate") {
					decompress = true;
					readError = BZlibCompressionAlgorithm()
						.CreateDecompressingOutputStream(&decompressorStorage,
							NULL, decompressingStream);
					if (readError != B_OK)
						break;

					decompressingStreamDeleter.SetTo(decompressingStream);
				}

				int32 index = fHeaders.HasHeader("Content-Length");
				if (index != B_ERROR)
					bytesTotal = atoi(fHeaders.HeaderAt(index).Value());
				else
					bytesTotal = -1;
			}
		}

		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) {
						readError = decompressingStream->WriteExactly(
							inputTempBuffer, bytesRead);
						if (readError != B_OK)
							break;

						ssize_t size = decompressorStorage.Size();
						BStackOrHeapArray<char, 4096> buffer(size);
						size = decompressorStorage.Read(buffer, size);
						if (size > 0) {
							fListener->DataReceived(this, buffer, bytesUnpacked,
								size);
							bytesUnpacked += size;
						}
					} else if (bytesRead > 0) {
						fListener->DataReceived(this, inputTempBuffer,
							bytesReceived - bytesRead, bytesRead);
					}
					fListener->DownloadProgress(this, bytesReceived,
						std::max((ssize_t)0, bytesTotal));
				}

				if (bytesTotal >= 0 && bytesReceived >= bytesTotal)
					receiveEnd = true;

				if (decompress && receiveEnd) {
					readError = decompressingStream->Flush();

					if (readError == B_BUFFER_OVERFLOW)
						readError = B_OK;

					if (readError != B_OK)
						break;

					ssize_t size = decompressorStorage.Size();
					BStackOrHeapArray<char, 4096> buffer(size);
					size = decompressorStorage.Read(buffer, size);
					if (fListener != NULL && size > 0) {
						fListener->DataReceived(this, buffer,
							bytesUnpacked, size);
						bytesUnpacked += size;
					}
				}
			}
		}

		parseEnd = (fInputBuffer.Size() == 0);
	}

	fSocket->Disconnect();
	delete[] inputTempBuffer;

	if (readError != B_OK)
		return readError;

	return fQuit ? B_INTERRUPTED : B_OK;
}
Пример #7
0
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;
}
Пример #8
0
void
BHttpRequest::_SendHeaders()
{
	// HTTP 1.1 additional headers
	if (fHttpVersion == B_HTTP_11) {
		fOutputHeaders.AddHeader("Host", Url().Host());

		fOutputHeaders.AddHeader("Accept", "*/*");
		fOutputHeaders.AddHeader("Accept-Encoding", "gzip,deflate");
			// Allow the remote server to send dynamic content by chunks
			// rather than waiting for the full content to be generated and
			// sending us data.

		fOutputHeaders.AddHeader("Connection", "close");
			// Let the remote server close the connection after response since
			// we don't handle multiple request on a single connection
	}

	// Classic HTTP headers
	if (fOptUserAgent.CountChars() > 0)
		fOutputHeaders.AddHeader("User-Agent", fOptUserAgent.String());

	if (fOptReferer.CountChars() > 0)
		fOutputHeaders.AddHeader("Referer", fOptReferer.String());

	// Authentication
	if (fContext != NULL) {
		BHttpAuthentication& authentication = fContext->GetAuthentication(fUrl);
		if (authentication.Method() != B_HTTP_AUTHENTICATION_NONE) {
			if (fOptUsername.Length() > 0) {
				authentication.SetUserName(fOptUsername);
				authentication.SetPassword(fOptPassword);
			}

			BString request(fRequestMethod);
			fOutputHeaders.AddHeader("Authorization",
				authentication.Authorization(fUrl, request));
		}
	}

	// Required headers for POST data
	if (fOptPostFields != NULL && fRequestMethod == B_HTTP_POST) {
		BString contentType;

		switch (fOptPostFields->GetFormType()) {
			case B_HTTP_FORM_MULTIPART:
				contentType << "multipart/form-data; boundary="
					<< fOptPostFields->GetMultipartBoundary() << "";
				break;

			case B_HTTP_FORM_URL_ENCODED:
				contentType << "application/x-www-form-urlencoded";
				break;
		}

		fOutputHeaders.AddHeader("Content-Type", contentType);
		fOutputHeaders.AddHeader("Content-Length",
			fOptPostFields->ContentLength());
	} else if (fOptInputData != NULL
			&& (fRequestMethod == B_HTTP_POST
			|| fRequestMethod == B_HTTP_PUT)) {
		if (fOptInputDataSize >= 0)
			fOutputHeaders.AddHeader("Content-Length", fOptInputDataSize);
		else
			fOutputHeaders.AddHeader("Transfer-Encoding", "chunked");
	}

	// Optional headers specified by the user
	if (fOptHeaders != NULL) {
		for (int32 headerIndex = 0; headerIndex < fOptHeaders->CountHeaders();
				headerIndex++) {
			BHttpHeader& optHeader = (*fOptHeaders)[headerIndex];
			int32 replaceIndex = fOutputHeaders.HasHeader(optHeader.Name());

			// Add or replace the current option header to the
			// output header list
			if (replaceIndex == -1)
				fOutputHeaders.AddHeader(optHeader.Name(), optHeader.Value());
			else
				fOutputHeaders[replaceIndex].SetValue(optHeader.Value());
		}
	}

	// Context cookies
	if (fOptSetCookies && fContext != NULL) {
		BString cookieString;

		BNetworkCookieJar::UrlIterator iterator
			= fContext->GetCookieJar().GetUrlIterator(fUrl);
		BNetworkCookie* cookie = iterator.Next();
		if (cookie != NULL) {
			while (true) {
				cookieString << cookie->RawCookie(false);
				cookie = iterator.Next();
				if (cookie == NULL)
					break;
				cookieString << "; ";
			}
	
			fOutputHeaders.AddHeader("Cookie", cookieString);
		}
	}

	// Write output headers to output stream
	for (int32 headerIndex = 0; headerIndex < fOutputHeaders.CountHeaders();
			headerIndex++) {
		const char* header = fOutputHeaders.HeaderAt(headerIndex).Header();
		fSocket->Write(header, strlen(header));
		fSocket->Write("\r\n", 2);

		_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", header);
	}
}
Пример #9
0
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;
}
Пример #10
0
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;
}
Пример #11
0
void
BGopherRequest::_ParseInput(bool last)
{
    BString line;

    while (_GetLine(line) == B_OK) {
        char type = GOPHER_TYPE_NONE;
        BStringList fields;

        line.MoveInto(&type, 0, 1);

        line.Split("\t", false, fields);

        if (type != GOPHER_TYPE_ENDOFPAGE
                && fields.CountStrings() < FIELD_GPFLAG)
            _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
                       "Unterminated gopher item (type '%c')", type);

        BString pageTitle;
        BString item;
        BString title = fields.StringAt(FIELD_NAME);
        BString link("gopher://");
        BString user;
        if (fields.CountStrings() > 3) {
            link << fields.StringAt(FIELD_HOST);
            if (fields.StringAt(FIELD_PORT).Length())
                link << ":" << fields.StringAt(FIELD_PORT);
            link << "/" << type;
            //if (fields.StringAt(FIELD_SELECTOR).ByteAt(0) != '/')
            //	link << "/";
            link << fields.StringAt(FIELD_SELECTOR);
        }
        _HTMLEscapeString(title);
        _HTMLEscapeString(link);

        switch (type) {
        case GOPHER_TYPE_ENDOFPAGE:
            /* end of the page */
            break;
        case GOPHER_TYPE_TEXTPLAIN:
            item << "<a href=\"" << link << "\">"
                 "<span class=\"text\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_BINARY:
        case GOPHER_TYPE_BINHEX:
        case GOPHER_TYPE_BINARCHIVE:
        case GOPHER_TYPE_UUENCODED:
            item << "<a href=\"" << link << "\">"
                 "<span class=\"binary\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_DIRECTORY:
            /*
             * directory link
             */
            item << "<a href=\"" << link << "\">"
                 "<span class=\"dir\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_ERROR:
            item << "<span class=\"error\">" << title << "</span>"
                 "<br/>\n";
            if (fPosition == 0 && pageTitle.Length() == 0)
                pageTitle << "Error: " << title;
            break;
        case GOPHER_TYPE_QUERY:
            /* TODO: handle search better.
             * For now we use an unnamed input field and accept sending ?=foo
             * as it seems at least Veronica-2 ignores the = but it's unclean.
             */
            item << "<form method=\"get\" action=\"" << link << "\" "
                 "onsubmit=\"window.location = this.action + '?' + "
                 "this.elements['q'].value; return false;\">"
                 "<span class=\"query\">"
                 "<label>" << title << " "
                 "<input id=\"q\" name=\"\" type=\"text\" align=\"right\" />"
                 "</label>"
                 "</span></form>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_TELNET:
            /* telnet: links
             * cf. gopher://78.80.30.202/1/ps3
             * -> gopher://78.80.30.202:23/8/ps3/new -> [email protected]
             */
            link = "telnet://";
            user = fields.StringAt(FIELD_SELECTOR);
            if (user.FindLast('/') > -1) {
                user.Remove(0, user.FindLast('/'));
                link << user << "@";
            }
            link << fields.StringAt(FIELD_HOST);
            if (fields.StringAt(FIELD_PORT) != "23")
                link << ":" << fields.StringAt(FIELD_PORT);

            item << "<a href=\"" << link << "\">"
                 "<span class=\"telnet\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_TN3270:
            /* tn3270: URI scheme, cf. http://tools.ietf.org/html/rfc6270 */
            link = "tn3270://";
            user = fields.StringAt(FIELD_SELECTOR);
            if (user.FindLast('/') > -1) {
                user.Remove(0, user.FindLast('/'));
                link << user << "@";
            }
            link << fields.StringAt(FIELD_HOST);
            if (fields.StringAt(FIELD_PORT) != "23")
                link << ":" << fields.StringAt(FIELD_PORT);

            item << "<a href=\"" << link << "\">"
                 "<span class=\"telnet\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_CSO_SEARCH:
            /* CSO search.
             * At least Lynx supports a cso:// URI scheme:
             * http://lynx.isc.org/lynx2.8.5/lynx2-8-5/lynx_help/lynx_url_support.html
             */
            link = "cso://";
            user = fields.StringAt(FIELD_SELECTOR);
            if (user.FindLast('/') > -1) {
                user.Remove(0, user.FindLast('/'));
                link << user << "@";
            }
            link << fields.StringAt(FIELD_HOST);
            if (fields.StringAt(FIELD_PORT) != "105")
                link << ":" << fields.StringAt(FIELD_PORT);

            item << "<a href=\"" << link << "\">"
                 "<span class=\"cso\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_GIF:
        case GOPHER_TYPE_IMAGE:
        case GOPHER_TYPE_PNG:
        case GOPHER_TYPE_BITMAP:
            /* quite dangerous, cf. gopher://namcub.accela-labs.com/1/pics */
            if (kInlineImages) {
                item << "<a href=\"" << link << "\">"
                     "<span class=\"img\">" << title << " "
                     "<img src=\"" << link << "\" "
                     "alt=\"" << title << "\"/>"
                     "</span></a>"
                     "<br/>\n";
                break;
            }
            /* fallback to default, link them */
            item << "<a href=\"" << link << "\">"
                 "<span class=\"img\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_HTML:
            /* cf. gopher://pineapple.vg/1 */
            if (fields.StringAt(FIELD_SELECTOR).StartsWith("URL:")) {
                link = fields.StringAt(FIELD_SELECTOR);
                link.Remove(0, 4);
            }
            /* cf. gopher://sdf.org/1/sdf/classes/ */

            item << "<a href=\"" << link << "\">"
                 "<span class=\"html\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_INFO:
            // TITLE resource, cf.
            // gopher://gophernicus.org/0/doc/gopher/gopher-title-resource.txt
            if (fPosition == 0 && pageTitle.Length() == 0
                    && fields.StringAt(FIELD_SELECTOR) == "TITLE") {
                pageTitle = title;
                break;
            }
            item << "<span class=\"info\">" << title << "</span>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_AUDIO:
        case GOPHER_TYPE_SOUND:
            item << "<a href=\"" << link << "\">"
                 "<span class=\"audio\">" << title << "</span></a>"
                 "<audio src=\"" << link << "\" "
                 //TODO:Fix crash in WebPositive with these
                 //"controls=\"controls\" "
                 //"width=\"300\" height=\"50\" "
                 "alt=\"" << title << "\"/>"
                 "<span>[player]</span></audio>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_PDF:
        case GOPHER_TYPE_DOC:
            /* generic case for known-to-work items */
            item << "<a href=\"" << link << "\">"
                 "<span class=\"document\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        case GOPHER_TYPE_MOVIE:
            item << "<a href=\"" << link << "\">"
                 "<span class=\"video\">" << title << "</span></a>"
                 "<video src=\"" << link << "\" "
                 //TODO:Fix crash in WebPositive with these
                 //"controls=\"controls\" "
                 //"width=\"300\" height=\"300\" "
                 "alt=\"" << title << "\"/>"
                 "<span>[player]</span></audio>"
                 "<br/>\n";
            break;
        default:
            _EmitDebug(B_URL_PROTOCOL_DEBUG_TEXT,
                       "Unknown gopher item (type 0x%02x '%c')", type, type);
            item << "<a href=\"" << link << "\">"
                 "<span class=\"unknown\">" << title << "</span></a>"
                 "<br/>\n";
            break;
        }

        if (fPosition == 0) {
            if (pageTitle.Length() == 0)
                pageTitle << "Index of " << Url();

            const char *uplink = ".";
            if (fPath.EndsWith("/"))
                uplink = "..";

            // emit header
            BString header;
            header <<
                   "<html>\n"
                   "<head>\n"
                   "<meta http-equiv=\"Content-Type\""
                   " content=\"text/html; charset=UTF-8\" />\n"
                   //FIXME: fix links
                   //"<link rel=\"icon\" type=\"image/png\""
                   //	" href=\"resource:icons/directory.png\">\n"
                   "<style type=\"text/css\">\n" << kStyleSheet << "</style>\n"
                   "<title>" << pageTitle << "</title>\n"
                   "</head>\n"
                   "<body id=\"gopher\">\n"
                   "<div class=\"uplink dontprint\">\n"
                   "<a href=" << uplink << ">[up]</a>\n"
                   "<a href=\"/\">[top]</a>\n"
                   "</div>\n"
                   "<h1>" << pageTitle << "</h1>\n";

            fListener->DataReceived(this, header.String(), fPosition,
                                    header.Length());

            fPosition += header.Length();
        }

        if (item.Length()) {
            fListener->DataReceived(this, item.String(), fPosition,
                                    item.Length());

            fPosition += item.Length();
        }
    }

    if (last) {
        // emit footer
        BString footer =
            "</div>\n"
            "</body>\n"
            "</html>\n";

        fListener->DataReceived(this, footer.String(), fPosition,
                                footer.Length());

        fPosition += footer.Length();
    }
}
Пример #12
0
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;
}
Пример #13
0
void
BHttpRequest::_AddOutputBufferLine(const char* line)
{
	_EmitDebug(B_URL_PROTOCOL_DEBUG_HEADER_OUT, "%s", line);
	fOutputBuffer << line << "\r\n";
}