Package net.pms.network

Source Code of net.pms.network.RequestHandler

/*
* PS3 Media Server, for streaming any medias to your PS3.
* Copyright (C) 2008  A.Brochard
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package net.pms.network;

import net.pms.PMS;
import net.pms.configuration.RendererConfiguration;
import net.pms.external.StartStopListenerDelegate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.StringTokenizer;

public class RequestHandler implements Runnable {
  private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class);
  public final static int SOCKET_BUF_SIZE = 32768;
  private Socket socket;
  private OutputStream output;
  private BufferedReader br;

  // Used to filter out known headers when the renderer is not recognized
  private final static String[] KNOWN_HEADERS = {
      "Accept",
      "Accept-Language",
      "Accept-Encoding",
      "Callback",
      "Connection",
      "Content-Length",
      "Content-Type",
      "Date",
      "Host",
      "Nt",
      "Sid",
      "Timeout",
      "User-Agent"
  };


  public RequestHandler(Socket socket) throws IOException {
    this.socket = socket;
    this.output = socket.getOutputStream();
    this.br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  }

  @Override
  public void run() {
    Request request = null;
    StartStopListenerDelegate startStopListenerDelegate = new StartStopListenerDelegate(socket.getInetAddress().getHostAddress());

    try {
      int receivedContentLength = -1;
      String headerLine = br.readLine();
      String userAgentString = null;
      StringBuilder unknownHeaders = new StringBuilder();
      String separator = "";
      RendererConfiguration renderer = null;

      InetSocketAddress remoteAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
      InetAddress ia = remoteAddress.getAddress();

      // Apply the IP filter
      if (filterIp(ia)) {
        throw new IOException("Access denied for address " + ia + " based on IP filter");
      }

      logger.trace("Opened request handler on socket " + socket);
      PMS.get().getRegistry().disableGoToSleep();

      while (headerLine != null && headerLine.length() > 0) {
        logger.trace("Received on socket: " + headerLine);

        // The request object is created inside the while loop.
        if (request != null && request.getMediaRenderer() == null) {
          // The handler makes a couple of attempts to recognize a renderer from its requests.
          // IP address matches from previous requests are preferred, when that fails request
          // header matches are attempted and if those fail as well we're stuck with the
          // default renderer.

          // Attempt 1: try to recognize the renderer by its socket address
          renderer = RendererConfiguration.getRendererConfigurationBySocketAddress(ia);

          if (renderer != null) {
            PMS.get().setRendererFound(renderer);
            request.setMediaRenderer(renderer);
            logger.trace("Matched media renderer \"" + renderer.getRendererName() + "\" based on address " + ia);
          }
        }

        if (renderer == null && headerLine != null
            && headerLine.toUpperCase().startsWith("USER-AGENT")
            && request != null) {
          userAgentString = headerLine.substring(headerLine.indexOf(":") + 1).trim();

          // Attempt 2: try to recognize the renderer by matching the "User-Agent" header
          renderer = RendererConfiguration.getRendererConfigurationByUA(userAgentString);

          if (renderer != null) {
            PMS.get().setRendererFound(renderer);
            request.setMediaRenderer(renderer);
            renderer.associateIP(ia)// Associate IP address for later requests
            logger.trace("Matched media renderer \"" + renderer.getRendererName() + "\" based on header \"" + headerLine + "\"");
          }
        }
        if (renderer == null && headerLine != null && request != null) {
          // Attempt 3: try to recognize the renderer by matching an additional header
          renderer = RendererConfiguration.getRendererConfigurationByUAAHH(headerLine);

          if (renderer != null) {
            PMS.get().setRendererFound(renderer);
            request.setMediaRenderer(renderer);
            renderer.associateIP(ia)// Associate IP address for later requests
            logger.trace("Matched media renderer \"" + renderer.getRendererName() + "\" based on header \"" + headerLine + "\"");
          }
        }
        try {
          StringTokenizer s = new StringTokenizer(headerLine);
          String temp = s.nextToken();
          if (temp.equals("SUBSCRIBE") || temp.equals("GET") || temp.equals("POST") || temp.equals("HEAD")) {
            request = new Request(temp, s.nextToken().substring(1));
            if (s.hasMoreTokens() && s.nextToken().equals("HTTP/1.0")) {
              request.setHttp10(true);
            }
          } else if (request != null && temp.toUpperCase().equals("CALLBACK:")) {
            request.setSoapaction(s.nextToken());
          } else if (request != null && temp.toUpperCase().equals("SOAPACTION:")) {
            request.setSoapaction(s.nextToken());
          } else if (headerLine.toUpperCase().contains("CONTENT-LENGTH:")) {
            receivedContentLength = Integer.parseInt(headerLine.substring(headerLine.toUpperCase().indexOf("CONTENT-LENGTH: ") + 16));
          } else if (headerLine.toUpperCase().indexOf("RANGE: BYTES=") > -1) {
            String nums = headerLine.substring(headerLine.toUpperCase().indexOf("RANGE: BYTES=") + 13).trim();
            StringTokenizer st = new StringTokenizer(nums, "-");
            if (!nums.startsWith("-")) {
              request.setLowRange(Long.parseLong(st.nextToken()));
            }
            if (!nums.startsWith("-") && !nums.endsWith("-")) {
              request.setHighRange(Long.parseLong(st.nextToken()));
            } else {
              request.setHighRange(-1);
            }
          } else if (headerLine.toLowerCase().indexOf("transfermode.dlna.org:") > -1) {
            request.setTransferMode(headerLine.substring(headerLine.toLowerCase().indexOf("transfermode.dlna.org:") + 22).trim());
          } else if (headerLine.toLowerCase().indexOf("getcontentfeatures.dlna.org:") > -1) {
            request.setContentFeatures(headerLine.substring(headerLine.toLowerCase().indexOf("getcontentfeatures.dlna.org:") + 28).trim());
          } else if (headerLine.toUpperCase().indexOf("TIMESEEKRANGE.DLNA.ORG: NPT=") > -1) { // firmware 2.50+
            String timeseek = headerLine.substring(headerLine.toUpperCase().indexOf("TIMESEEKRANGE.DLNA.ORG: NPT=") + 28);
            if (timeseek.endsWith("-")) {
              timeseek = timeseek.substring(0, timeseek.length() - 1);
            } else if (timeseek.indexOf("-") > -1) {
              timeseek = timeseek.substring(0, timeseek.indexOf("-"));
            }
            request.setTimeseek(Double.parseDouble(timeseek));
          } else if (headerLine.toUpperCase().indexOf("TIMESEEKRANGE.DLNA.ORG : NPT=") > -1) { // firmware 2.40
            String timeseek = headerLine.substring(headerLine.toUpperCase().indexOf("TIMESEEKRANGE.DLNA.ORG : NPT=") + 29);
            if (timeseek.endsWith("-")) {
              timeseek = timeseek.substring(0, timeseek.length() - 1);
            } else if (timeseek.indexOf("-") > -1) {
              timeseek = timeseek.substring(0, timeseek.indexOf("-"));
            }
            request.setTimeseek(Double.parseDouble(timeseek));
          } else {
            /*
             * If we made it to here, none of the previous header checks matched.
             * Unknown headers make interesting logging info when we cannot recognize
             * the media renderer, so keep track of the truly unknown ones.
             */
            boolean isKnown = false;

            // Try to match possible known headers.
            for (String knownHeaderString : KNOWN_HEADERS) {
              if (headerLine.toLowerCase().startsWith(knownHeaderString.toLowerCase())) {
                isKnown = true;
                break;
              }
            }

            if (!isKnown) {
              // Truly unknown header, therefore interesting. Save for later use.
              unknownHeaders.append(separator).append(headerLine);
              separator = ", ";
            }
          }
        } catch (Exception e) {
          logger.error("Error in parsing HTTP headers", e);
        }

        headerLine = br.readLine();
      }

      if (request != null) {
        // Still no media renderer recognized?
        if (request.getMediaRenderer() == null) {
          // Attempt 4: Not really an attempt; all other attempts to recognize
          // the renderer have failed. The only option left is to assume the
          // default renderer.
          request.setMediaRenderer(RendererConfiguration.getDefaultConf());
          logger.trace("Using default media renderer: " + request.getMediaRenderer().getRendererName());

          if (userAgentString != null && !userAgentString.equals("FDSSDP")) {
            // We have found an unknown renderer
            logger.info("Media renderer was not recognized. Possible identifying HTTP headers: User-Agent: "  + userAgentString
                + ("".equals(unknownHeaders.toString()) ? "" : ", " + unknownHeaders.toString()));
            PMS.get().setRendererFound(request.getMediaRenderer());
          }
        } else {
          if (userAgentString != null) {
            logger.debug("HTTP User-Agent: " + userAgentString);
          }
          logger.trace("Recognized media renderer: " + request.getMediaRenderer().getRendererName());
        }
      }

      if (receivedContentLength > 0) {
        char buf[] = new char[receivedContentLength];
        br.read(buf);
        if (request != null) {
          request.setTextContent(new String(buf));
        }
      }

      if (request != null) {
        logger.trace("HTTP: " + request.getArgument() + " / " + request.getLowRange() + "-" + request.getHighRange());
      }

      if (request != null) {
        request.answer(output, startStopListenerDelegate);
      }

      if (request != null && request.getInputStream() != null) {
        request.getInputStream().close();
      }
    } catch (IOException e) {
      logger.trace("Unexpected IO error: " + e.getClass().getName() + ": " + e.getMessage());
      if (request != null && request.getInputStream() != null) {
        try {
          logger.trace("Closing input stream: " + request.getInputStream());
          request.getInputStream().close();
        } catch (IOException e1) {
          logger.error("Error closing input stream", e1);
        }
      }
    } finally {
      try {
        PMS.get().getRegistry().reenableGoToSleep();
        output.close();
        br.close();
        socket.close();
      } catch (IOException e) {
        logger.error("Error closing connection: ", e);
      }

      startStopListenerDelegate.stop();
      logger.trace("Close connection");
    }
  }

  /**
   * Applies the IP filter to the specified internet address. Returns true
   * if the address is not allowed and therefore should be filtered out,
   * false otherwise.
   *
   * @param inetAddress The internet address to verify.
   * @return True when not allowed, false otherwise.
   */
  private boolean filterIp(InetAddress inetAddress) {
    return !PMS.getConfiguration().getIpFiltering().allowed(inetAddress);
  }
}
TOP

Related Classes of net.pms.network.RequestHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.