Пример #1
0
void serveStreamAdv(AbortableTask *task, const MediaStore::FileDetail& detail, AdvStreamReader *reader, const FrontEnd::RequestContext& reqCtx, const NPT_HttpRequest *req, NPT_HttpResponse& resp, HttpOutput *httpOutput, NPT_Size bufferSize)
{
	bool isGetMethod = req->GetMethod().Compare("GET") == 0;
	bool isHeadMethod = req->GetMethod().Compare("HEAD") == 0;
	if (isGetMethod || isHeadMethod) {
		NPT_Result nr;
		NPT_UInt64 offset, length;

		NPT_HttpHeader *hdrRange = req->GetHeaders().GetHeader("RANGE");
		if (hdrRange) {
			if (!parseRangeHeader(hdrRange->GetValue(), detail.m_size, offset, length)) {
				setStatusCode(resp, 416);
				httpOutput->writeResponseHeader(resp);
				return;
			}
			setStatusCode(resp, 206);
			resp.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, NPT_String::Format("bytes %s-%s/%s", NPT_String::FromIntegerU(offset).GetChars(), NPT_String::FromIntegerU(offset + length - 1).GetChars(), NPT_String::FromIntegerU(detail.m_size).GetChars()));
		} else {
			offset = 0;
			length = detail.m_size;
			setStatusCode(resp, 200);
		}

		resp.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, detail.m_mimeType);
		resp.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, NPT_String::FromIntegerU(length));
		resp.GetHeaders().SetHeader("Last-Modified", NPT_DateTime(detail.m_modificationTime).ToString(NPT_DateTime::FORMAT_RFC_1123));
		resp.GetHeaders().SetHeader("Accept-Ranges", "bytes");

		resp.GetHeaders().SetHeader("EXT", "");
		if (reqCtx.transferMode != FrontEnd::TM_None) {
			const char *transferMode = "Streaming";
			switch (reqCtx.transferMode) {
			case FrontEnd::TM_Background:
				transferMode = "Background";
				break;
			case FrontEnd::TM_Interactive:
				transferMode = "Interactive";
				break;
			}
			resp.GetHeaders().SetHeader("transferMode.dlna.org", transferMode);
		}

		if (reqCtx.getcontentFeaturesReq) {
			NPT_String contentFeatures("DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000");
			resp.GetHeaders().SetHeader("contentFeatures.dlna.org", contentFeatures);
		}

		httpOutput->writeResponseHeader(resp);

		if (isGetMethod) {

			bool abortFlag = false;
			ServeFileAbortCallback abortCallback(&abortFlag, reader);

			if (task->registerAbortCallback(&abortCallback)) {

				reader->seek(offset);

				NPT_DataBuffer buffer(bufferSize);

				NPT_UInt64 cbRemain = length;

				for (;;) {
					if (abortFlag) {
						break;
					}

					if (cbRemain == 0) {
						break;
					}

					NPT_Size cbRead;
					NPT_Size cbToRead = cbRemain;
					if (cbToRead > buffer.GetBufferSize()) {
						cbToRead = buffer.GetBufferSize();
					}
					nr = reader->read(buffer.UseData(), buffer.GetBufferSize(), &cbRead);
					if (NPT_FAILED(nr)) {
						/*if (nr == NPT_ERROR_EOS) {
						} else {
						}*/
						break;
					}

					if (abortFlag) {
						break;
					}

					if (cbRead > 0) {
						cbRemain -= cbRead;
						httpOutput->writeData(buffer.GetData(), cbRead);
					}
				}

				task->unregisterAbortCallback(&abortCallback);
			}
		}
	} else {
		setStatusCode(resp, 405);
		resp.GetHeaders().SetHeader("Allow", "GET, HEAD");
		httpOutput->writeResponseHeader(resp);
	}
}
Пример #2
0
void RTSPServer::RTSPClientSession
	::handleCmd_PLAY(ServerMediaSubsession* subsession, char const* cseq,
			 char const* fullRequestStr) {
  char* rtspURL = fOurServer.rtspURL(fOurServerMediaSession, fClientSocket);
  unsigned rtspURLSize = strlen(rtspURL);

	// Parse the client's "Scale:" header, if any:
  float scale;
  Boolean sawScaleHeader = parseScaleHeader(fullRequestStr, scale);

	// Try to set the stream's scale factor to this value:
  if (subsession == NULL /*aggregate op*/) {
	  fOurServerMediaSession->testScaleFactor(scale);
	} else {
	  subsession->testScaleFactor(scale);
	}

  char buf[100];
  char* scaleHeader;
  if (!sawScaleHeader) {
	  buf[0] = '\0'; // Because we didn't see a Scale: header, don't send one back
	} else {
	  sprintf(buf, "Scale: %f\r\n", scale);
	}
  scaleHeader = strDup(buf);

	// Parse the client's "Range:" header, if any:
  double rangeStart = 0.0, rangeEnd = 0.0;
  Boolean sawRangeHeader = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd);

	// Use this information, plus the stream's duration (if known), to create
	// our own "Range:" header, for the response:
  float duration = subsession == NULL /*aggregate op*/
		? fOurServerMediaSession->duration() : subsession->duration();
  if (duration < 0.0) {
		// We're an aggregate PLAY, but the subsessions have different durations.
		// Use the largest of these durations in our header
	  duration = -duration;
	}

  if (rangeEnd <= 0.0 || rangeEnd > duration) rangeEnd = duration;
  if (rangeStart < 0.0) {
	  rangeStart = 0.0;
	} else if (rangeEnd > 0.0 && scale > 0.0 && rangeStart > rangeEnd) {
	  rangeStart = rangeEnd;
	}

  char* rangeHeader;
  if (!sawRangeHeader) {
	  buf[0] = '\0'; // Because we didn't see a Range: header, don't send one back
	} else if (rangeEnd == 0.0 && scale >= 0.0) {
	  sprintf(buf, "Range: npt=%.3f-\r\n", rangeStart);
	} else {
	  sprintf(buf, "Range: npt=%.3f-%.3f\r\n", rangeStart, rangeEnd);
	}
  rangeHeader = strDup(buf);

	// Create a "RTP-Info:" line.  It will get filled in from each subsession's state:
  char const* rtpInfoFmt =
		"%s" // "RTP-Info:", plus any preceding rtpInfo items
		"%s" // comma separator, if needed
		"url=%s/%s"
		";seq=%d"
		";rtptime=%u"
		;
  unsigned rtpInfoFmtSize = strlen(rtpInfoFmt);
  char* rtpInfo = strDup("RTP-Info: ");
  unsigned i, numRTPInfoItems = 0;

	// Do any required seeking/scaling on each subsession, before starting streaming:
  for (i = 0; i < fNumStreamStates; ++i) {
	  if (subsession == NULL /* means: aggregated operation */
	|| subsession == fStreamStates[i].subsession) {
		  if (sawScaleHeader) {
	fStreamStates[i].subsession->setStreamScale(fOurSessionId,
							  fStreamStates[i].streamToken,
							  scale);
			}
		  if (sawRangeHeader) {
	fStreamStates[i].subsession->seekStream(fOurSessionId,
						fStreamStates[i].streamToken,
						rangeStart);
			}
		}
	}

	// Now, start streaming:
  for (i = 0; i < fNumStreamStates; ++i) {
	  if (subsession == NULL /* means: aggregated operation */
	|| subsession == fStreamStates[i].subsession) {
		  unsigned short rtpSeqNum = 0;
		  unsigned rtpTimestamp = 0;
		  fStreamStates[i].subsession->startStream(fOurSessionId,
								 fStreamStates[i].streamToken,
								 (TaskFunc*)noteClientLiveness,
								 this,			//save client session pointer for livenessTimeoutTask
								 rtpSeqNum,
								 rtpTimestamp);
		  const char *urlSuffix = fStreamStates[i].subsession->trackId();
		  char* prevRTPInfo = rtpInfo;
		  unsigned rtpInfoSize = rtpInfoFmtSize
	+ strlen(prevRTPInfo)
	+ 1
	+ rtspURLSize + strlen(urlSuffix)
	+ 5 /*max unsigned short len*/
	+ 10 /*max unsigned (32-bit) len*/
	+ 2 /*allows for trailing \r\n at final end of string*/;
		  rtpInfo = new char[rtpInfoSize];
		  sprintf(rtpInfo, rtpInfoFmt,
			  prevRTPInfo,
			  numRTPInfoItems++ == 0 ? "" : ",",
			  rtspURL, urlSuffix,
			  rtpSeqNum,
			  rtpTimestamp
				);
		  delete[] prevRTPInfo;
		}
	}
  if (numRTPInfoItems == 0) {
	  rtpInfo[0] = '\0';
	} else {
	  unsigned rtpInfoLen = strlen(rtpInfo);
	  rtpInfo[rtpInfoLen] = '\r';
	  rtpInfo[rtpInfoLen+1] = '\n';
	  rtpInfo[rtpInfoLen+2] = '\0';
	}

	// Fill in the response:
  snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
		 "RTSP/1.0 200 OK\r\n"
		 "CSeq: %s\r\n"
		 "%s"
		 "%s"
		 "%s"
		 "Session: %08X\r\n"
		 "%s\r\n",
		 cseq,
		 dateHeader(),
		 scaleHeader,
		 rangeHeader,
		 fOurSessionId,
		 rtpInfo);
  delete[] rtpInfo; delete[] rangeHeader;
  delete[] scaleHeader; delete[] rtspURL;
}
Пример #3
0
void serveFile2(AbortableTask *task, const NPT_String& filePath, const NPT_String& mimeType, const FrontEnd::RequestContext& reqCtx, const NPT_HttpRequest *req, NPT_HttpResponse& resp, HttpOutput *httpOutput)
{
#if 1
	NPT_File f(filePath);
	NPT_FileInfo fileInfo;
	if (NPT_FAILED(f.GetInfo(fileInfo)) || fileInfo.m_Type != NPT_FileInfo::FILE_TYPE_REGULAR || NPT_FAILED(f.Open(NPT_FILE_OPEN_MODE_READ))) {
		setStatusCode(resp, 404);
		httpOutput->writeResponseHeader(resp);
		return;
	}

	NPT_InputStreamReference fileInput;
	f.GetInputStream(fileInput);
	serveStream(task, fileInput.AsPointer(), fileInfo.m_ModificationTime, mimeType, reqCtx, req, resp, httpOutput);
#else
	bool isGetMethod = req->GetMethod().Compare("GET") == 0;
	bool isHeadMethod = req->GetMethod().Compare("HEAD") == 0;
	if (isGetMethod || isHeadMethod) {
		NPT_Result nr;
		NPT_File f(filePath);
		NPT_FileInfo fileInfo;
		if (NPT_FAILED(f.GetInfo(fileInfo)) || fileInfo.m_Type != NPT_FileInfo::FILE_TYPE_REGULAR || NPT_FAILED(f.Open(NPT_FILE_OPEN_MODE_READ))) {
			setStatusCode(resp, 404);
			httpOutput->writeResponseHeader(resp);
			return;
		}

		NPT_InputStreamReference fileInput;
		f.GetInputStream(fileInput);

		NPT_UInt64 offset, length;

		NPT_HttpHeader *hdrRange = req->GetHeaders().GetHeader("RANGE");
		if (hdrRange) {
			if (!parseRangeHeader(hdrRange->GetValue(), fileInfo.m_Size, offset, length)) {
				setStatusCode(resp, 416);
				httpOutput->writeResponseHeader(resp);
				return;
			}
			setStatusCode(resp, 206);
			resp.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, NPT_String::Format("bytes %s-%s/%s", NPT_String::FromIntegerU(offset).GetChars(), NPT_String::FromIntegerU(offset + length - 1).GetChars(), NPT_String::FromIntegerU(fileInfo.m_Size).GetChars()));
			fileInput->Seek(offset);
		} else {
			offset = 0;
			length = fileInfo.m_Size;
			setStatusCode(resp, 200);
		}

		resp.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, mimeType);
		resp.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, NPT_String::FromIntegerU(length));
		resp.GetHeaders().SetHeader("Last-Modified", NPT_DateTime(fileInfo.m_ModificationTime).ToString(NPT_DateTime::FORMAT_RFC_1123));
		resp.GetHeaders().SetHeader("Accept-Ranges", "bytes");

		resp.GetHeaders().SetHeader("EXT", "");
		if (reqCtx.transferMode != FrontEnd::TM_None) {
			const char *transferMode = "Streaming";
			switch (reqCtx.transferMode) {
			case FrontEnd::TM_Background:
				transferMode = "Background";
				break;
			case FrontEnd::TM_Interactive:
				transferMode = "Interactive";
				break;
			}
			resp.GetHeaders().SetHeader("transferMode.dlna.org", transferMode);
		}

		if (reqCtx.getcontentFeaturesReq) {
			NPT_String contentFeatures("DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000");
			resp.GetHeaders().SetHeader("contentFeatures.dlna.org", contentFeatures);
		}

		httpOutput->writeResponseHeader(resp);

		if (isGetMethod) {

			bool abortFlag = false;
			ServeFileAbortCallback abortCallback(&abortFlag);

			if (task->registerAbortCallback(&abortCallback)) {
				NPT_DataBuffer buffer(4096);

				NPT_UInt64 cbRemain = length;

				for (;;) {
					if (abortFlag) {
						break;
					}

					if (cbRemain == 0) {
						break;
					}

					NPT_Size cbRead;
					NPT_UInt64 cbToRead = cbRemain;
					if (cbToRead > buffer.GetBufferSize()) {
						cbToRead = buffer.GetBufferSize();
					}
					nr = fileInput->Read(buffer.UseData(), buffer.GetBufferSize(), &cbRead);
					if (NPT_FAILED(nr)) {
						/*if (nr == NPT_ERROR_EOS) {
						} else {
						}*/
						break;
					}

					if (abortFlag) {
						break;
					}

					if (cbRead > 0) {
						cbRemain -= cbRead;
						httpOutput->writeData(buffer.GetData(), cbRead);
					}
				}

				task->unregisterAbortCallback(&abortCallback);
			}
		}
	} else {
		setStatusCode(resp, 405);
		resp.GetHeaders().SetHeader("Allow", "GET, HEAD");
		httpOutput->writeResponseHeader(resp);
	}
#endif
}
Пример #4
0
void RTSPServer::RTSPClientSession
::handleCmd_SETUP(char const* cseq,
		  char const* urlPreSuffix, char const* urlSuffix,
		  char const* fullRequestStr) {
	// "urlPreSuffix" should be the session (stream) name, and
	// "urlSuffix" should be the subsession (track) name.
  char const* streamName = urlPreSuffix;
  char const* trackId = urlSuffix;

	// Check whether we have existing session state, and, if so, whether it's
	// for the session that's named in "streamName".	(Note that we don't
	// support more than one concurrent session on the same client connection.) #####
  if (fOurServerMediaSession != NULL
			&& strcmp(streamName, fOurServerMediaSession->streamName()) != 0) {
	  fOurServerMediaSession = NULL;
	}
  if (fOurServerMediaSession == NULL) {
		// Set up this session's state.

		// Look up the "ServerMediaSession" object for the specified stream:
	  if (streamName[0] != '\0' ||
	fOurServer.lookupServerMediaSession("") != NULL) { // normal case
		} else { // weird case: there was no track id in the URL
		  streamName = urlSuffix;
		  trackId = NULL;
		}
	  fOurServerMediaSession = fOurServer.lookupServerMediaSession(streamName);
	  if (fOurServerMediaSession == NULL) {
		  handleCmd_notFound(cseq);
		  return;
		}

	  fOurServerMediaSession->incrementReferenceCount();

		// Set up our array of states for this session's subsessions (tracks):
	  reclaimStreamStates();
	  ServerMediaSubsessionIterator iter(*fOurServerMediaSession);
	  for (fNumStreamStates = 0; iter.next() != NULL; ++fNumStreamStates) {}
	  fStreamStates = new struct streamState[fNumStreamStates];
	  iter.reset();
	  ServerMediaSubsession* subsession;
	  for (unsigned i = 0; i < fNumStreamStates; ++i) {
		  subsession = iter.next();
		  fStreamStates[i].subsession = subsession;
		  fStreamStates[i].streamToken = NULL; // for now; reset by SETUP later
		}
	}

	// Look up information for the specified subsession (track):
  ServerMediaSubsession* subsession = NULL;
  unsigned streamNum;
  if (trackId != NULL && trackId[0] != '\0') { // normal case
	  for (streamNum = 0; streamNum < fNumStreamStates; ++streamNum) {
		  subsession = fStreamStates[streamNum].subsession;
		  if (subsession != NULL && strcmp(trackId, subsession->trackId()) == 0) break;
		}
	  if (streamNum >= fNumStreamStates) {
			// The specified track id doesn't exist, so this request fails:
		  handleCmd_notFound(cseq);
		  return;
		}
	} else {
		// Weird case: there was no track id in the URL.
		// This works only if we have only one subsession:
	  if (fNumStreamStates != 1) {
		  handleCmd_bad(cseq);
		  return;
		}
	  streamNum = 0;
	  subsession = fStreamStates[streamNum].subsession;
	}
	// ASSERT: subsession != NULL

	// Look for a "Transport:" header in the request string,
	// to extract client parameters:
  StreamingMode streamingMode;
  char* streamingModeString = NULL; // set when RAW_UDP streaming is specified
  char* clientsDestinationAddressStr;
  u_int8_t clientsDestinationTTL;
  portNumBits clientRTPPortNum, clientRTCPPortNum;
  unsigned char rtpChannelId, rtcpChannelId;
  parseTransportHeader(fullRequestStr, streamingMode, streamingModeString, fIsMulticast,
					 clientsDestinationAddressStr, clientsDestinationTTL,
					 clientRTPPortNum, clientRTCPPortNum,
					 rtpChannelId, rtcpChannelId);
  if (streamingMode == RTP_TCP && rtpChannelId == 0xFF) {
		// TCP streaming was requested, but with no "interleaving=" fields.
		// (QuickTime Player sometimes does this.)  Set the RTP and RTCP channel ids to
		// proper values:
	  rtpChannelId = fTCPStreamIdCount; rtcpChannelId = fTCPStreamIdCount+1;
	}
  fTCPStreamIdCount += 2;

  Port clientRTPPort(clientRTPPortNum);
  Port clientRTCPPort(clientRTCPPortNum);

	// Next, check whether a "Range:" header is present in the request.
	// This isn't legal, but some clients do this to combine "SETUP" and "PLAY":
  double rangeStart = 0.0, rangeEnd = 0.0;
  fStreamAfterSETUP = parseRangeHeader(fullRequestStr, rangeStart, rangeEnd) ||
										  parsePlayNowHeader(fullRequestStr);

	// Then, get server parameters from the 'subsession':
  int tcpSocketNum = streamingMode == RTP_TCP ? fClientSocket : -1;
  netAddressBits destinationAddress = 0;
  u_int8_t destinationTTL = 5;
#ifdef RTSP_ALLOW_CLIENT_DESTINATION_SETTING
  if (clientsDestinationAddressStr != NULL) {
		// Use the client-provided "destination" address.
		// Note: This potentially allows the server to be used in denial-of-service
		// attacks, so don't enable this code unless you're sure that clients are
		// trusted.
	  destinationAddress = our_inet_addr(clientsDestinationAddressStr);
	}
	// Also use the client-provided TTL.
  destinationTTL = clientsDestinationTTL;
#endif
  delete[] clientsDestinationAddressStr;
  Port serverRTPPort(0);
  Port serverRTCPPort(0);
  subsession->getStreamParameters(fOurSessionId, fClientAddr.sin_addr.s_addr,
				  clientRTPPort, clientRTCPPort,
				  tcpSocketNum, rtpChannelId, rtcpChannelId,
				  destinationAddress, destinationTTL, fIsMulticast,
				  serverRTPPort, serverRTCPPort,
				  fStreamStates[streamNum].streamToken);
  struct in_addr destinationAddr; destinationAddr.s_addr = destinationAddress;
  char* destAddrStr = strDup(our_inet_ntoa(destinationAddr));
  struct sockaddr_in sourceAddr; SOCKLEN_T namelen = sizeof sourceAddr;
  getsockname(fClientSocket, (struct sockaddr*)&sourceAddr, &namelen);
  char* sourceAddrStr = strDup(our_inet_ntoa(sourceAddr.sin_addr));
  if (fIsMulticast) {
	  switch (streamingMode) {
	  case RTP_UDP:
			  snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
								 "RTSP/1.0 200 OK\r\n"
								 "CSeq: %s\r\n"
								 "%s"
								 "Transport: RTP/AVP;multicast;destination=%s;source=%s;port=%d-%d;ttl=%d\r\n"
								 "Session: %08X\r\n\r\n",
								 cseq,
								 dateHeader(),
								 destAddrStr, sourceAddrStr, ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()), destinationTTL,
								 fOurSessionId);
			  break;
	  case RTP_TCP:
				// multicast streams can't be sent via TCP
			  handleCmd_unsupportedTransport(cseq);
			  break;
	  case RAW_UDP:
			  snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
								 "RTSP/1.0 200 OK\r\n"
								 "CSeq: %s\r\n"
								 "%s"
								 "Transport: %s;multicast;destination=%s;source=%s;port=%d;ttl=%d\r\n"
								 "Session: %08X\r\n\r\n",
								 cseq,
								 dateHeader(),
								 streamingModeString, destAddrStr, sourceAddrStr, ntohs(serverRTPPort.num()), destinationTTL,
								 fOurSessionId);
			  break;
		}
	} else {
	  switch (streamingMode) {
	  case RTP_UDP: {
		  snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
				 "RTSP/1.0 200 OK\r\n"
				 "CSeq: %s\r\n"
				 "%s"
				 "Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\n"
				 "Session: %08X\r\n\r\n",
				 cseq,
				 dateHeader(),
				 destAddrStr, sourceAddrStr, ntohs(clientRTPPort.num()), ntohs(clientRTCPPort.num()), ntohs(serverRTPPort.num()), ntohs(serverRTCPPort.num()),
				 fOurSessionId);
		  break;
		}
	  case RTP_TCP: {
		  snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
				 "RTSP/1.0 200 OK\r\n"
				 "CSeq: %s\r\n"
				 "%s"
				 "Transport: RTP/AVP/TCP;unicast;destination=%s;source=%s;interleaved=%d-%d\r\n"
				 "Session: %08X\r\n\r\n",
				 cseq,
				 dateHeader(),
				 destAddrStr, sourceAddrStr, rtpChannelId, rtcpChannelId,
				 fOurSessionId);
		  break;
		}
	  case RAW_UDP: {
		  snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
				 "RTSP/1.0 200 OK\r\n"
				 "CSeq: %s\r\n"
				 "%s"
				 "Transport: %s;unicast;destination=%s;source=%s;client_port=%d;server_port=%d\r\n"
				 "Session: %08X\r\n\r\n",
				 cseq,
				 dateHeader(),
				 streamingModeString, destAddrStr, sourceAddrStr, ntohs(clientRTPPort.num()), ntohs(serverRTPPort.num()),
				 fOurSessionId);
		  break;
		}
		}
	}
  delete[] destAddrStr; delete[] sourceAddrStr; delete[] streamingModeString;
}