OpenSimMirror/OpenSim/Region/ScriptEngine/XMREngine/MMRWebRequest.cs

270 lines
11 KiB
C#

/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @brief Perform web request
*/
using System;
using System.IO;
using System.Net;
using System.Text;
namespace OpenSim.Region.ScriptEngine.XMREngine {
public class MMRWebRequest {
public static bool allowFileURL = false;
public static Stream MakeRequest (string verb, string requestUrl, string obj, int timeoutms)
{
/*
* Pick apart the given URL and make sure we support it.
* For file:// URLs, just return a read-only stream of the file.
*/
Uri uri = new Uri (requestUrl);
string supported = "http and https";
if (allowFileURL && (verb == "GET")) {
supported = "file, http and https";
if (uri.Scheme == "file") {
return File.OpenRead (requestUrl.Substring (7));
}
}
bool https = uri.Scheme == "https";
if (!https && (uri.Scheme != "http")) {
throw new WebException ("only support " + supported + ", not " + uri.Scheme + " in " + requestUrl);
}
string host = uri.Host;
int port = uri.Port;
if (port < 0) port = https ? 443 : 80;
string path = uri.AbsolutePath;
/*
* Connect to the web server.
*/
System.Net.Sockets.TcpClient tcpconnection = new System.Net.Sockets.TcpClient (host, port);
if (timeoutms > 0) {
tcpconnection.SendTimeout = timeoutms;
tcpconnection.ReceiveTimeout = timeoutms;
}
try {
/*
* Get TCP stream to/from web server.
* If HTTPS, wrap stream with SSL encryption.
*/
Stream tcpstream = tcpconnection.GetStream ();
if (https) {
System.Net.Security.SslStream sslstream = new System.Net.Security.SslStream (tcpstream, false);
sslstream.AuthenticateAsClient (host);
tcpstream = sslstream;
}
/*
* Write request header to the web server.
* There might be some POST data as well to write to web server.
*/
WriteStream (tcpstream, verb + " " + path + " HTTP/1.1\r\n");
WriteStream (tcpstream, "Host: " + host + "\r\n");
if (obj != null) {
byte[] bytes = Encoding.UTF8.GetBytes (obj);
WriteStream (tcpstream, "Content-Length: " + bytes.Length + "\r\n");
WriteStream (tcpstream, "Content-Type: application/x-www-form-urlencoded\r\n");
WriteStream (tcpstream, "\r\n");
tcpstream.Write (bytes, 0, bytes.Length);
} else {
WriteStream (tcpstream, "\r\n");
}
tcpstream.Flush ();
/*
* Check for successful reply status line.
*/
string headerline = ReadStreamLine (tcpstream).Trim ();
if (headerline != "HTTP/1.1 200 OK") throw new WebException ("status line " + headerline);
/*
* Scan through header lines.
* The only ones we care about are Content-Length and Transfer-Encoding.
*/
bool chunked = false;
int contentlength = -1;
while ((headerline = ReadStreamLine (tcpstream).Trim ().ToLowerInvariant ()) != "") {
if (headerline.StartsWith ("content-length:")) {
contentlength = int.Parse (headerline.Substring (15));
}
if (headerline.StartsWith ("transfer-encoding:") && (headerline.Substring (18).Trim () == "chunked")) {
chunked = true;
}
}
/*
* Read response byte array as a series of chunks.
*/
if (chunked) {
return new ChunkedStreamReader (tcpstream);
}
/*
* Read response byte array with the exact length given by Content-Length.
*/
if (contentlength >= 0) {
return new LengthStreamReader (tcpstream, contentlength);
}
/*
* Don't know how it is being transferred.
*/
throw new WebException ("header missing content-length or transfer-encoding: chunked");
} catch {
tcpconnection.Close ();
throw;
}
}
/**
* @brief Write the string out as ASCII bytes.
*/
private static void WriteStream (Stream stream, string line)
{
byte[] bytes = Encoding.ASCII.GetBytes (line);
stream.Write (bytes, 0, bytes.Length);
}
/**
* @brief Read the next text line from a stream.
* @returns string with \r\n trimmed off
*/
private static string ReadStreamLine (Stream stream)
{
StringBuilder sb = new StringBuilder ();
while (true) {
int b = stream.ReadByte ();
if (b < 0) break;
if (b == '\n') break;
if (b == '\r') continue;
sb.Append ((char)b);
}
return sb.ToString ();
}
private class ChunkedStreamReader : Stream {
private int chunklen;
private Stream tcpstream;
public ChunkedStreamReader (Stream tcpstream)
{
this.tcpstream = tcpstream;
}
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return false; } }
public override bool CanTimeout { get { return false; } }
public override bool CanWrite { get { return false; } }
public override long Length { get { return 0; } }
public override long Position { get { return 0; } set { } }
public override void Flush () { }
public override long Seek (long offset, SeekOrigin origin) { return 0; }
public override void SetLength (long length) { }
public override void Write (byte[] buffer, int offset, int length) { }
public override int Read (byte[] buffer, int offset, int length)
{
if (length <= 0) return 0;
if (chunklen == 0) {
chunklen = int.Parse (ReadStreamLine (tcpstream), System.Globalization.NumberStyles.HexNumber);
if (chunklen < 0) throw new WebException ("negative chunk length");
if (chunklen == 0) chunklen = -1;
}
if (chunklen < 0) return 0;
int maxread = (length < chunklen) ? length : chunklen;
int lenread = tcpstream.Read (buffer, offset, maxread);
chunklen -= lenread;
if (chunklen == 0) {
int b = tcpstream.ReadByte ();
if (b == '\r') b = tcpstream.ReadByte ();
if (b != '\n') throw new WebException ("chunk not followed by \\r\\n");
}
return lenread;
}
public override void Close ()
{
chunklen = -1;
if (tcpstream != null) {
tcpstream.Close ();
tcpstream = null;
}
}
}
private class LengthStreamReader : Stream {
private int contentlength;
private Stream tcpstream;
public LengthStreamReader (Stream tcpstream, int contentlength)
{
this.tcpstream = tcpstream;
this.contentlength = contentlength;
}
public override bool CanRead { get { return true; } }
public override bool CanSeek { get { return false; } }
public override bool CanTimeout { get { return false; } }
public override bool CanWrite { get { return false; } }
public override long Length { get { return 0; } }
public override long Position { get { return 0; } set { } }
public override void Flush () { }
public override long Seek (long offset, SeekOrigin origin) { return 0; }
public override void SetLength (long length) { }
public override void Write (byte[] buffer, int offset, int length) { }
public override int Read (byte[] buffer, int offset, int length)
{
if (length <= 0) return 0;
if (contentlength <= 0) return 0;
int maxread = (length < contentlength) ? length : contentlength;
int lenread = tcpstream.Read (buffer, offset, maxread);
contentlength -= lenread;
return lenread;
}
public override void Close ()
{
contentlength = -1;
if (tcpstream != null) {
tcpstream.Close ();
tcpstream = null;
}
}
}
}
}