//--- Class Methods --- /// <summary> /// Retrieve user credentials from a request uri and/or headers. /// </summary> /// <param name="uri">Request uri.</param> /// <param name="headers">Request headers.</param> /// <param name="username">Parsed user name.</param> /// <param name="password">Parsed password.</param> /// <returns><see langword="True"/> if the credentials were succesfully parsed from request information.</returns> public static bool GetAuthentication(Uri uri, DreamHeaders headers, out string username, out string password) { username = null; password = null; // Authorization = Basic 1YJ1TTpPcmx4bMQ= // check if a user/password pair was provided in the URI if (!string.IsNullOrEmpty(uri.UserInfo)) { string[] userPwd = uri.UserInfo.Split(new char[] { ':' }, 2); username = XUri.Decode(userPwd[0]); password = XUri.Decode((userPwd.Length > 1) ? userPwd[1] : string.Empty); return(true); } else { // check if authorization is in the request header string header = headers[DreamHeaders.AUTHORIZATION]; if (!string.IsNullOrEmpty(header)) { // extract authorization data string[] value = header.Split(new char[] { ' ' }, 2); if ((value.Length == 2) && StringUtil.EqualsInvariantIgnoreCase(value[0], "Basic")) { string[] userPwd = Encoding.UTF8.GetString(Convert.FromBase64String(value[1])).Split(new char[] { ':' }, 2); username = userPwd[0]; password = (userPwd.Length > 1) ? userPwd[1] : string.Empty; return(true); } } } return(false); }
//--- Constructors --- internal MockPlugInvocation(string verb, XUri uri, DreamMessage request, DreamHeaders responseHeaders) { Verb = verb; Uri = uri; Request = request; ResponseHeaders = responseHeaders; }
public void Parsing_unquoted_IfNoneMatch_does_not_alter_etag() { var ifNoneMatch = "dsfsdfsdfsdfsdf"; var rawHeaders = new NameValueCollection { { DreamHeaders.IF_NONE_MATCH, ifNoneMatch } }; var headers = new DreamHeaders(rawHeaders); Assert.AreEqual(ifNoneMatch, headers.IfNoneMatch); }
public void Parsing_single_quoted_IfNoneMatch_removes_quotes() { var ifNoneMatch = "dsfsdfsdfsdfsdf"; var rawHeaders = new NameValueCollection { { DreamHeaders.IF_NONE_MATCH, "'" + ifNoneMatch + "'" } }; var headers = new DreamHeaders(rawHeaders); Assert.AreEqual(ifNoneMatch, headers.IfNoneMatch); }
public void Parsing_unquoted_etag_does_not_alter_etag() { var etag = "dsfsdfsdfsdfsdf"; var rawHeaders = new NameValueCollection { { "etag", etag } }; var headers = new DreamHeaders(rawHeaders); Assert.AreEqual(etag, headers.ETag); }
public void Parsing_single_quoted_etag_removes_quotes() { var etag = "dsfsdfsdfsdfsdf"; var rawHeaders = new NameValueCollection { { "etag", "'" + etag + "'" } }; var headers = new DreamHeaders(rawHeaders); Assert.AreEqual(etag, headers.ETag); }
public void Get_via_internal_routing_follows_301_but_expects_query_to_be_in_location() { using (var hostInfo = DreamTestHelper.CreateRandomPortHost()) { var mock = MockService.CreateMockService(hostInfo); var redirectCalled = 0; var targetCalled = 0; mock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result <DreamMessage> response) { var msg = "nothing here"; var q = context.Uri.GetParam("q"); var forward = context.Uri.GetParam("forward"); if (context.Uri.LastSegment == "redirect") { _log.Debug("called redirect"); var redirect = context.Service.Self.Uri.At("target").AsPublicUri(); if (forward == "true") { redirect = redirect.With("q", q); } redirectCalled++; var headers = new DreamHeaders(); headers.Add(DreamHeaders.LOCATION, redirect.ToString()); response.Return(new DreamMessage(DreamStatus.MovedPermanently, headers)); return; } if (context.Uri.LastSegment == "target") { _log.Debug("called target"); if (q == "x") { _log.Debug("target request had query"); targetCalled++; response.Return(DreamMessage.Ok()); return; } response.Return(DreamMessage.BadRequest("missing query param")); return; } _log.DebugFormat("called uri: {0} => {1}", context.Uri, msg); response.Return(DreamMessage.NotFound(msg)); }; var uri = mock.AtLocalMachine.At("redirect"); _log.DebugFormat("calling redirect service at {0}", uri); var r = Plug.New(uri).With("q", "x").Get(new Result <DreamMessage>()).Wait(); Assert.AreEqual(DreamStatus.BadRequest, r.Status); Assert.AreEqual(1, redirectCalled, "redirect without forward called incorrectly"); Assert.AreEqual(0, targetCalled, "target without forward called incorrectly"); redirectCalled = 0; targetCalled = 0; r = Plug.New(uri).With("q", "x").With("forward", "true").Get(new Result <DreamMessage>()).Wait(); Assert.IsTrue(r.IsSuccessful, r.HasDocument ? r.ToDocument()["message"].AsText : "request failed: " + r.Status); Assert.AreEqual(1, redirectCalled, "redirect with forward called incorrectly"); Assert.AreEqual(1, targetCalled, "target with forward called incorrectly"); } }
public void Rendering_etag_leaves_single_quoted_value_alone() { var etag = "dsfsdfsdfsdfsdf"; var headers = new DreamHeaders { ETag = "'" + etag + "'" }; var httpRequest = (HttpWebRequest)WebRequest.Create("http://localhost"); HttpUtil.AddHeader(httpRequest, DreamHeaders.ETAG, headers.ETag); Assert.AreEqual("'" + etag + "'", httpRequest.Headers[DreamHeaders.ETAG]); }
public void Preserve_order_of_hosts_in_forwarded_for_header() { // X-Forwarded-For var collections = new NameValueCollection(); collections.Add("X-Forwarded-For", "a, b, c"); collections.Add("X-Forwarded-For", "d, e"); collections.Add("X-Forwarded-For", "f, g, h"); var headers = new DreamHeaders(collections); var values = headers.ForwardedFor; Assert.AreEqual(new []{ "a", "b", "c", "d", "e", "f", "g", "h" }, values); }
internal static XDoc ExecuteScript(Plug env, DreamHeaders headers, XDoc script) { // execute script commands XDoc reply = new XDoc("results"); string ID = script["@ID"].Contents; if(!string.IsNullOrEmpty(ID)) { reply.Attr("ID", ID); } foreach(XDoc cmd in script.Elements) { reply.Add(ExecuteCommand(env, headers, cmd)); } return reply; }
public void Preserve_order_of_hosts_in_forwarded_for_header() { // X-Forwarded-For var collections = new NameValueCollection(); collections.Add("X-Forwarded-For", "a, b, c"); collections.Add("X-Forwarded-For", "d, e"); collections.Add("X-Forwarded-For", "f, g, h"); var headers = new DreamHeaders(collections); var values = headers.ForwardedFor; Assert.AreEqual(new [] { "a", "b", "c", "d", "e", "f", "g", "h" }, values); }
public void Get_via_internal_routing_follows_301_and_forwards_headers() { using (var hostInfo = DreamTestHelper.CreateRandomPortHost()) { var mock = MockService.CreateMockService(hostInfo); var redirectCalled = 0; var targetCalled = 0; mock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result <DreamMessage> response) { var msg = "nothing here"; var h = request.Headers["h"]; if (context.Uri.LastSegment == "redirect") { _log.Debug("called redirect"); if (h == "y") { redirectCalled++; var headers = new DreamHeaders(); headers.Add(DreamHeaders.LOCATION, context.Service.Self.Uri.At("target").AsPublicUri().ToString()); response.Return(new DreamMessage(DreamStatus.MovedPermanently, headers)); return; } msg = "redirect request lacked header"; } if (context.Uri.LastSegment == "target") { _log.Debug("called target"); if (h == "y") { _log.Debug("target request had header"); targetCalled++; response.Return(DreamMessage.Ok()); return; } msg = "target request lacked header ({1}"; } _log.DebugFormat("called uri: {0} => {1}", context.Uri, msg); response.Return(DreamMessage.NotFound(msg)); }; var uri = mock.AtLocalMachine.At("redirect"); _log.DebugFormat("calling redirect service at {0}", uri); var r = Plug.New(uri).WithHeader("h", "y").Get(new Result <DreamMessage>()).Wait(); Assert.IsTrue(r.IsSuccessful, r.HasDocument ? r.ToDocument()["message"].AsText : "request failed: " + r.Status); Assert.AreEqual(1, redirectCalled, "redirect called incorrectly"); Assert.AreEqual(1, targetCalled, "target called incorrectly"); } }
//--- Methods --- internal static XDoc ExecuteCommand(Plug env, DreamHeaders headers, XDoc cmd) { try { switch(cmd.Name.ToLowerInvariant()) { case "script": return ExecuteScript(env, headers, cmd); case "fork": return ExecuteFork(env, headers, cmd); case "action": return ExecuteAction(env, headers, cmd); case "pipe": return ExecutePipe(env, headers, cmd); default: throw new DreamException(string.Format("unregonized script command: " + cmd.Name.ToString())); } } catch(Exception e) { return new XException(e); } }
public void Can_parse_bad_namevaluecollection_from_HttpContext() { var collections = new NameValueCollection { { "Cookie", "__utma=134392366.2030730348.1275932450.1276553042.1276556836.19; __utmz=134392366.1276207881.9.3.utmcsr=developer.mindtouch.com|utmccn=(referral)|utmcmd=referral|utmcct=/User:arnec/bugs; _mkto_trk=id:954-WGP-507&token:_mch-mindtouch.com-1270756717014-83706; WRUID=0; __kti=1274382964652" }, { "Cookie", "http%3A%2F%2Fwww.mindtouch.com%2F" }, { "Cookie", "; __ktv=2f4-f02d-634b-51e2128b724d7c2; __qca=P0-2102347259-1274460371553; PHPSESSID=307e779182909ab37932b4dffe77c40a; __utmc=134392366; __kts=1274382964673,http%3A%2F%2Fwww.mindtouch.com%2F,; __ktt=631f-d0a2-648e-e0b128b724d7c2; authtoken=\"1_634121336269193470_4254e33b49bc1ee0a72c5716200e296b\"; __utmb=134392366.6.10.1276556836" } }; Assert.AreEqual(3, collections.GetValues("Cookie").Length); var headers = new DreamHeaders(collections); var cookies = headers.Cookies; Assert.AreEqual(13, cookies.Count); Assert.AreEqual("__utma", cookies[0].Name); Assert.AreEqual("134392366.2030730348.1275932450.1276553042.1276556836.19", cookies[0].Value); Assert.AreEqual("__utmz", cookies[1].Name); Assert.AreEqual("134392366.1276207881.9.3.utmcsr=developer.mindtouch.com|utmccn=(referral)|utmcmd=referral|utmcct=/User:arnec/bugs", cookies[1].Value); Assert.AreEqual("_mkto_trk", cookies[2].Name); Assert.AreEqual("id:954-WGP-507&token:_mch-mindtouch.com-1270756717014-83706", cookies[2].Value); Assert.AreEqual("WRUID", cookies[3].Name); Assert.AreEqual("0", cookies[3].Value); Assert.AreEqual("__kti", cookies[4].Name); Assert.AreEqual("1274382964652,http%3A%2F%2Fwww.mindtouch.com%2F,", cookies[4].Value); Assert.AreEqual("__ktv", cookies[5].Name); Assert.AreEqual("2f4-f02d-634b-51e2128b724d7c2", cookies[5].Value); Assert.AreEqual("__qca", cookies[6].Name); Assert.AreEqual("P0-2102347259-1274460371553", cookies[6].Value); Assert.AreEqual("PHPSESSID", cookies[7].Name); Assert.AreEqual("307e779182909ab37932b4dffe77c40a", cookies[7].Value); Assert.AreEqual("__utmc", cookies[8].Name); Assert.AreEqual("134392366", cookies[8].Value); Assert.AreEqual("__kts", cookies[9].Name); Assert.AreEqual("1274382964673,http%3A%2F%2Fwww.mindtouch.com%2F,", cookies[9].Value); Assert.AreEqual("__ktt", cookies[10].Name); Assert.AreEqual("631f-d0a2-648e-e0b128b724d7c2", cookies[10].Value); Assert.AreEqual("authtoken", cookies[11].Name); Assert.AreEqual("1_634121336269193470_4254e33b49bc1ee0a72c5716200e296b", cookies[11].Value); Assert.AreEqual("__utmb", cookies[12].Name); Assert.AreEqual("134392366.6.10.1276556836", cookies[12].Value); }
/// <summary> /// Create a new <see cref="DreamMessage"/> instance containing payload and envelope. /// </summary> /// <returns></returns> public DreamMessage AsMessage() { DreamHeaders headers = new DreamHeaders(); headers.DreamEventId = Id; if (Resource != null) { headers.DreamEventResource = Resource.ToString(); } headers.DreamEventChannel = Channel.ToString(); string[] origin = new string[Origins.Length]; for (int i = 0; i < Origins.Length; i++) { origin[i] = Origins[i].ToString(); } headers.DreamEventOrigin = origin; string[] recipients = new string[Recipients.Length]; for (int i = 0; i < Recipients.Length; i++) { recipients[i] = Recipients[i].ToString(); } headers.DreamEventRecipients = recipients; string[] via = new string[Via.Length]; for (int i = 0; i < Via.Length; i++) { via[i] = Via[i].ToString(); } headers.DreamEventVia = via; // if our message has a document as content, we can skip the whole stream business if (_message.HasDocument) { return(new DreamMessage(DreamStatus.Ok, headers, _message.ToDocument())); } // AsBytes will create the byte array only once, so we can call this multiple times without penalty byte[] bytes = _message.ToBytes(); return(DreamMessage.Ok(_message.ContentType, bytes)); }
internal static XDoc ExecuteFork(Plug env, DreamHeaders headers, XDoc fork) { // execute script commands XDoc reply = new XDoc("results"); string ID = fork["@ID"].Contents; if(!string.IsNullOrEmpty(ID)) { reply.Attr("ID", ID); } // TODO (steveb): we should use a 'fixed capacity' cue which marks itself as done when 'count' is reached XDoc forks = fork.Elements; foreach(XDoc cmd in forks) { // TODO (steveb): we should be doing this in parallel, not sequentially! try { reply.Add(ExecuteCommand(env, headers, cmd)); } catch(Exception e) { reply.Add(new XException(e)); } } return reply; }
public void Get_via_internal_routing_follows_301_but_expects_query_to_be_in_location() { using(var hostInfo = DreamTestHelper.CreateRandomPortHost()) { var mock = MockService.CreateMockService(hostInfo); var redirectCalled = 0; var targetCalled = 0; mock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result<DreamMessage> response) { var msg = "nothing here"; var q = context.Uri.GetParam("q"); var forward = context.Uri.GetParam("forward"); if(context.Uri.LastSegment == "redirect") { _log.Debug("called redirect"); var redirect = context.Service.Self.Uri.At("target").AsPublicUri(); if(forward == "true") { redirect = redirect.With("q", q); } redirectCalled++; var headers = new DreamHeaders(); headers.Add(DreamHeaders.LOCATION, redirect.ToString()); response.Return(new DreamMessage(DreamStatus.MovedPermanently, headers)); return; } if(context.Uri.LastSegment == "target") { _log.Debug("called target"); if(q == "x") { _log.Debug("target request had query"); targetCalled++; response.Return(DreamMessage.Ok()); return; } response.Return(DreamMessage.BadRequest("missing query param")); return; } _log.DebugFormat("called uri: {0} => {1}", context.Uri, msg); response.Return(DreamMessage.NotFound(msg)); }; var uri = mock.AtLocalMachine.At("redirect"); _log.DebugFormat("calling redirect service at {0}", uri); var r = Plug.New(uri).With("q", "x").GetAsync().Wait(); Assert.AreEqual(DreamStatus.BadRequest, r.Status); Assert.AreEqual(1, redirectCalled, "redirect without forward called incorrectly"); Assert.AreEqual(0, targetCalled, "target without forward called incorrectly"); redirectCalled = 0; targetCalled = 0; r = Plug.New(uri).With("q", "x").With("forward", "true").GetAsync().Wait(); Assert.IsTrue(r.IsSuccessful, r.HasDocument ? r.ToDocument()["message"].AsText : "request failed: " + r.Status); Assert.AreEqual(1, redirectCalled, "redirect with forward called incorrectly"); Assert.AreEqual(1, targetCalled, "target with forward called incorrectly"); } }
public void Rendering_etag_quotes_unquoted_value() { var etag = "dsfsdfsdfsdfsdf"; var headers = new DreamHeaders { ETag = etag }; var httpRequest = (HttpWebRequest)WebRequest.Create("http://localhost"); HttpUtil.AddHeader(httpRequest, DreamHeaders.ETAG, headers.ETag); Assert.AreEqual("\"" + etag + "\"", httpRequest.Headers[DreamHeaders.ETAG]); }
/// <summary> /// Create a new message. /// </summary> /// <param name="status">Http status.</param> /// <param name="headers">Header collection.</param> /// <param name="contentType">Content Mime-Type</param> /// <param name="contentLength">Content byte langth</param> /// <param name="stream">Stream to uas as the source for the message's content.</param> public DreamMessage(DreamStatus status, DreamHeaders headers, MimeType contentType, long contentLength, Stream stream) { this.Status = status; this.Headers = new DreamHeaders(headers); if(contentLength != -1) { this.Headers.ContentLength = contentLength; } this.Headers.ContentType = contentType ?? MimeType.DefaultMimeType; // set stream _stream = stream ?? Stream.Null; _streamOpen = !_stream.IsStreamMemorized(); }
/// <summary> /// Create a copy of the instance with a header added. /// </summary> /// <param name="name">Header name.</param> /// <param name="value">Header value.</param> /// <returns>New instance.</returns> public Plug WithHeader(string name, string value) { if(name == null) { throw new ArgumentNullException("name"); } if(value == null) { throw new ArgumentNullException("value"); } DreamHeaders newHeaders = new DreamHeaders(_headers); newHeaders.Add(name, value); return new Plug(Uri, Timeout, newHeaders, _preHandlers, _postHandlers, Credentials, _cookieJarOverride, MaxAutoRedirects); }
/// <summary> /// Create a new <see cref="DreamMessage"/> instance containing payload and envelope. /// </summary> /// <returns></returns> public DreamMessage AsMessage() { DreamHeaders headers = new DreamHeaders(); headers.DreamEventId = Id; if(Resource != null) { headers.DreamEventResource = Resource.ToString(); } headers.DreamEventChannel = Channel.ToString(); string[] origin = new string[Origins.Length]; for(int i = 0; i < Origins.Length; i++) { origin[i] = Origins[i].ToString(); } headers.DreamEventOrigin = origin; string[] recipients = new string[Recipients.Length]; for(int i = 0; i < Recipients.Length; i++) { recipients[i] = Recipients[i].ToString(); } headers.DreamEventRecipients = recipients; string[] via = new string[Via.Length]; for(int i = 0; i < Via.Length; i++) { via[i] = Via[i].ToString(); } headers.DreamEventVia = via; // if our message has a document as content, we can skip the whole stream business if(_message.HasDocument) { return new DreamMessage(DreamStatus.Ok, headers, _message.ToDocument()); } // AsBytes will create the byte array only once, so we can call this multiple times without penalty byte[] bytes = _message.ToBytes(); return DreamMessage.Ok(_message.ContentType, bytes); }
private static DreamMessage PostProcess(string verb, XUri uri, XUri normalizedUri, DreamHeaders headers, DreamCookieJar cookies, DreamMessage message) { // check if we received cookies if(message.HasCookies) { // add matching cookies to service or to global cookie jar if(cookies != null) { lock(cookies) { if(!StringUtil.EqualsInvariant(uri.Scheme, "local") && StringUtil.EqualsInvariant(normalizedUri.Scheme, "local")) { // need to translate cookies as they leave the dreamcontext cookies.Update(DreamCookie.ConvertToPublic(message.Cookies), uri); } else { cookies.Update(message.Cookies, uri); } } } } return message; }
//--- Constructors --- /// <summary> /// Create a new instance. /// </summary> /// <param name="uri">Uri to the resource to make the request against.</param> /// <param name="timeout">Invocation timeout.</param> /// <param name="headers">Header collection for request.</param> /// <param name="preHandlers">Optional pre-invocation handlers.</param> /// <param name="postHandlers">Optional post-invocation handlers.</param> /// <param name="credentials">Optional request credentials.</param> /// <param name="cookieJarOverride">Optional cookie jar to override global jar shared by <see cref="Plug"/> instances.</param> /// <param name="maxAutoRedirects">Maximum number of redirects to follow, 0 if non redirects should be followed.</param> public Plug(XUri uri, TimeSpan timeout, DreamHeaders headers, List<PlugHandler> preHandlers, List<PlugHandler> postHandlers, ICredentials credentials, DreamCookieJar cookieJarOverride, ushort maxAutoRedirects) { if(uri == null) { throw new ArgumentNullException("uri"); } this.Uri = uri; this.Timeout = timeout; this.Credentials = credentials; _headers = headers; _preHandlers = preHandlers; _postHandlers = postHandlers; _cookieJarOverride = cookieJarOverride; _maxAutoRedirects = maxAutoRedirects; }
/// <summary> /// Create a new message. /// </summary> /// <param name="status">Http status.</param> /// <param name="headers">Header collection.</param> /// <param name="contentType">Content Mime-Type.</param> /// <param name="doc">Message body.</param> public DreamMessage(DreamStatus status, DreamHeaders headers, MimeType contentType, XDoc doc) { if(doc == null) { throw new ArgumentNullException("doc"); } this.Status = status; this.Headers = new DreamHeaders(headers); // check if document is empty if(doc.IsEmpty) { // we store empty XML documents as text content; it causes less confusion for browsers this.Headers.ContentType = MimeType.TEXT; this.Headers.ContentLength = 0L; _doc = doc; _bytes = new byte[0]; } else { this.Headers.ContentType = contentType ?? MimeType.XML; _doc = doc.Clone(); } }
/// <summary> /// Create a new message. /// </summary> /// <param name="status">Http status.</param> /// <param name="headers">Header collection.</param> /// <param name="contentType">Content Mime-Type.</param> /// <param name="text">Message body.</param> public DreamMessage(DreamStatus status, DreamHeaders headers, MimeType contentType, string text) : this(status, headers, contentType, contentType.CharSet.GetBytes(text)) { }
/// <summary> /// Create a new message. /// </summary> /// <param name="status">Http status.</param> /// <param name="headers">Header collection.</param> /// <param name="contentType">Content Mime-Type.</param> /// <param name="bytes">Message body.</param> public DreamMessage(DreamStatus status, DreamHeaders headers, MimeType contentType, byte[] bytes) { if(bytes == null) { throw new ArgumentNullException("bytes"); } this.Status = status; this.Headers = new DreamHeaders(headers); this.Headers.ContentLength = bytes.LongLength; this.Headers.ContentType = contentType ?? MimeType.DefaultMimeType; // set bytes _bytes = bytes; }
/// <summary> /// WARNING: This method is thread-blocking. Please avoid using it if possible. /// </summary> internal static XDoc ExecuteAction(Plug env, DreamHeaders headers, XDoc action) { string verb = action["@verb"].Contents; string path = action["@path"].Contents; if((path.Length > 0) && (path[0] == '/')) { path = path.Substring(1); } XUri uri; if(!XUri.TryParse(path, out uri)) { uri = env.Uri.AtAbsolutePath(path); } // create message DreamMessage message = DreamMessage.Ok(GetActionBody(action)); message.Headers.AddRange(headers); // apply headers foreach(XDoc header in action["header"]) { message.Headers[header["@name"].Contents] = header.Contents; } // BUG #814: we need to support events // execute action DreamMessage reply = Plug.New(uri).Invoke(verb, message); // prepare response XDoc result = new XMessage(reply); string ID = action["@ID"].Contents; if(!string.IsNullOrEmpty(ID)) { result.Root.Attr("ID", ID); } return result; }
/// <summary> /// Create a copy of the instance with a header collection added. /// </summary> /// <param name="headers">Header collection</param> /// <returns>New instance.</returns> public Plug WithHeaders(DreamHeaders headers) { if(headers != null) { DreamHeaders newHeaders = new DreamHeaders(_headers); newHeaders.AddRange(headers); return new Plug(Uri, Timeout, newHeaders, _preHandlers, _postHandlers, Credentials, _cookieJarOverride, MaxAutoRedirects); } return this; }
public void Get_via_internal_routing_follows_301_and_forwards_headers() { using(var hostInfo = DreamTestHelper.CreateRandomPortHost()) { var mock = MockService.CreateMockService(hostInfo); var redirectCalled = 0; var targetCalled = 0; mock.Service.CatchAllCallback = delegate(DreamContext context, DreamMessage request, Result<DreamMessage> response) { var msg = "nothing here"; var h = request.Headers["h"]; if(context.Uri.LastSegment == "redirect") { _log.Debug("called redirect"); if(h == "y") { redirectCalled++; var headers = new DreamHeaders(); headers.Add(DreamHeaders.LOCATION, context.Service.Self.Uri.At("target").AsPublicUri().ToString()); response.Return(new DreamMessage(DreamStatus.MovedPermanently, headers)); return; } msg = "redirect request lacked header"; } if(context.Uri.LastSegment == "target") { _log.Debug("called target"); if(h == "y") { _log.Debug("target request had header"); targetCalled++; response.Return(DreamMessage.Ok()); return; } msg = "target request lacked header ({1}"; } _log.DebugFormat("called uri: {0} => {1}", context.Uri, msg); response.Return(DreamMessage.NotFound(msg)); }; var uri = mock.AtLocalMachine.At("redirect"); _log.DebugFormat("calling redirect service at {0}", uri); var r = Plug.New(uri).WithHeader("h", "y").GetAsync().Wait(); Assert.IsTrue(r.IsSuccessful, r.HasDocument ? r.ToDocument()["message"].AsText : "request failed: " + r.Status); Assert.AreEqual(1, redirectCalled, "redirect called incorrectly"); Assert.AreEqual(1, targetCalled, "target called incorrectly"); } }
/// <summary> /// Create a copy of the instance with a header removed. /// </summary> /// <param name="name">Name of the header to remove.</param> /// <returns>New instance.</returns> public Plug WithoutHeader(string name) { DreamHeaders newHeaders = null; if(_headers != null) { newHeaders = new DreamHeaders(_headers); newHeaders.Remove(name); if(newHeaders.Count == 0) { newHeaders = null; } } return new Plug(Uri, Timeout, newHeaders, _preHandlers, _postHandlers, Credentials, _cookieJarOverride, MaxAutoRedirects); }
//--- Class Methods --- /// <summary> /// Retrieve user credentials from a request uri and/or headers. /// </summary> /// <param name="uri">Request uri.</param> /// <param name="headers">Request headers.</param> /// <param name="username">Parsed user name.</param> /// <param name="password">Parsed password.</param> /// <returns><see langword="True"/> if the credentials were succesfully parsed from request information.</returns> public static bool GetAuthentication(Uri uri, DreamHeaders headers, out string username, out string password) { username = null; password = null; // Authorization = Basic 1YJ1TTpPcmx4bMQ= // check if a user/password pair was provided in the URI if(!string.IsNullOrEmpty(uri.UserInfo)) { var userPwd = uri.UserInfo.Split(new[] { ':' }, 2); username = XUri.Decode(userPwd[0]); password = XUri.Decode((userPwd.Length > 1) ? userPwd[1] : string.Empty); return true; } // check if authorization is in the request header var header = headers[DreamHeaders.AUTHORIZATION]; if(!string.IsNullOrEmpty(header)) { // extract authorization data var value = header.Split(new[] { ' ' }, 2); if((value.Length == 2) && value[0].EqualsInvariantIgnoreCase("Basic")) { try { var userPwd = Encoding.UTF8.GetString(Convert.FromBase64String(value[1])).Split(new[] { ':' }, 2); username = userPwd[0]; password = (userPwd.Length > 1) ? userPwd[1] : string.Empty; return true; } catch { throw new InvalidHttpAuthorizationHeader(); } } } return false; }
/// <summary> /// WARNING: This method is thread-blocking. Please avoid using it if possible. /// </summary> internal static XDoc ExecutePipe(Plug env, DreamHeaders headers, XDoc pipe) { DreamMessage message = null; foreach(XDoc action in pipe["action"]) { string verb = action["@verb"].Contents; string path = action["@path"].Contents; XUri uri; if(!XUri.TryParse(path, out uri)) { uri = env.Uri.AtPath(path); } // create first message if(message == null) { message = DreamMessage.Ok(GetActionBody(action)); } message.Headers.AddRange(headers); // apply headers foreach(XDoc header in action["header"]) { message.Headers[header["@name"].Contents] = header.Contents; } // execute action message = Plug.New(uri).Invoke(verb, message); if(!message.IsSuccessful) { break; } } // prepare response if(message == null) { return XDoc.Empty; } XDoc result = new XMessage(message); string ID = pipe["@ID"].Contents; if(!string.IsNullOrEmpty(ID)) { result.Root.Attr("ID", ID); } return result; }
//--- Constructors --- /// <summary> /// Create a new message. /// </summary> /// <param name="status">Http status.</param> /// <param name="headers">Header collection.</param> public DreamMessage(DreamStatus status, DreamHeaders headers) { this.Status = status; this.Headers = new DreamHeaders(headers); _bytes = new byte[0]; _noContent = true; }
private static DreamMessage PreProcess(string verb, XUri uri, XUri normalizedUri, DreamHeaders headers, DreamCookieJar cookies, DreamMessage message) { // check if plug is running in the context of a service DreamContext context = DreamContext.CurrentOrNull; if(context != null) { // set request id header message.Headers.DreamRequestId = context.GetState<string>(DreamHeaders.DREAM_REQUEST_ID); // set dream service header if(context.Service.Self != null) { message.Headers.DreamService = context.AsPublicUri(context.Service.Self).ToString(); } // check if uri is local:// if(normalizedUri.Scheme.EqualsInvariant("local")) { DreamUtil.AppendHeadersToInternallyForwardedMessage(context.Request, message); } } if(cookies != null) { lock(cookies) { message.Cookies.AddRange(cookies.Fetch(uri)); } } // transfer plug headers message.Headers.AddRange(headers); return message; }
/// <summary> /// Create a new message. /// </summary> /// <param name="status">Http status.</param> /// <param name="headers">Header collection.</param> /// <param name="doc">Message body.</param> public DreamMessage(DreamStatus status, DreamHeaders headers, XDoc doc) : this(status, headers, MimeType.XML, doc) { }