// https://tools.ietf.org/html/rfc7239#section-6 private void AppendNode(IPAddress ipAddress, int port, NodeFormat format, StringBuilder builder) { // "It is important to note that an IPv6 address and any nodename with // node-port specified MUST be quoted, since ":" is not an allowed // character in "token"." var addPort = port != 0 && (format == NodeFormat.IpAndPort || format == NodeFormat.UnknownAndPort || format == NodeFormat.RandomAndPort); var ipv6 = (format == NodeFormat.Ip || format == NodeFormat.IpAndPort) && ipAddress != null && ipAddress.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6; var quote = addPort || ipv6; if (quote) { builder.Append("\""); } switch (format) { case NodeFormat.Ip: case NodeFormat.IpAndPort: if (ipAddress != null) { if (ipv6) { builder.Append("["); } builder.Append(ipAddress.ToString()); if (ipv6) { builder.Append("]"); } break; } // This primarily happens in test environments that don't use real connections. goto case NodeFormat.Unknown; case NodeFormat.Unknown: case NodeFormat.UnknownAndPort: builder.Append("unknown"); break; case NodeFormat.Random: case NodeFormat.RandomAndPort: AppendRandom(builder); break; default: throw new NotImplementedException(format.ToString()); } if (addPort) { builder.Append(":"); builder.Append(port); } if (quote) { builder.Append("\""); } }
/// <summary> /// Clones the route and adds the transform which will add the Forwarded header as defined by [RFC 7239](https://tools.ietf.org/html/rfc7239). /// </summary> public static RouteConfig WithTransformForwarded(this RouteConfig route, bool useHost = true, bool useProto = true, NodeFormat forFormat = NodeFormat.Random, NodeFormat byFormat = NodeFormat.Random, ForwardedTransformActions action = ForwardedTransformActions.Set) { var headers = new List <string>(); if (forFormat != NodeFormat.None) { headers.Add(ForwardedTransformFactory.ForKey); } if (byFormat != NodeFormat.None) { headers.Add(ForwardedTransformFactory.ByKey); } if (useHost) { headers.Add(ForwardedTransformFactory.HostKey); } if (useProto) { headers.Add(ForwardedTransformFactory.ProtoKey); } return(route.WithTransform(transform => { transform[ForwardedTransformFactory.ForwardedKey] = string.Join(',', headers); transform[ForwardedTransformFactory.ActionKey] = action.ToString(); if (forFormat != NodeFormat.None) { transform.Add(ForwardedTransformFactory.ForFormatKey, forFormat.ToString()); } if (byFormat != NodeFormat.None) { transform.Add(ForwardedTransformFactory.ByFormatKey, byFormat.ToString()); } })); }
/// <summary> /// Clones the route and adds the transform which will add the Forwarded header as defined by [RFC 7239](https://tools.ietf.org/html/rfc7239). /// </summary> public static ProxyRoute WithTransformForwarded(this ProxyRoute proxyRoute, bool useHost = true, bool useProto = true, NodeFormat forFormat = NodeFormat.Random, NodeFormat byFormat = NodeFormat.Random, bool append = true) { var headers = new List <string>(); if (forFormat != NodeFormat.None) { headers.Add(ForwardedTransformFactory.ForKey); } if (byFormat != NodeFormat.None) { headers.Add(ForwardedTransformFactory.ByKey); } if (useHost) { headers.Add(ForwardedTransformFactory.HostKey); } if (useProto) { headers.Add(ForwardedTransformFactory.ProtoKey); } return(proxyRoute.WithTransform(transform => { transform[ForwardedTransformFactory.ForwardedKey] = string.Join(',', headers); transform[ForwardedTransformFactory.AppendKey] = append.ToString(); if (forFormat != NodeFormat.None) { transform.Add(ForwardedTransformFactory.ForFormatKey, forFormat.ToString()); } if (byFormat != NodeFormat.None) { transform.Add(ForwardedTransformFactory.ByFormatKey, byFormat.ToString()); } })); }
// Given either Twine, JSON or XML input, return an array // containing info about the nodes in that file public static NodeInfo[] GetNodesFromText(string text, NodeFormat format) { // All the nodes we found in this file var nodes = new List <NodeInfo> (); switch (format) { case NodeFormat.SingleNodeText: // If it starts with a comment, treat it as a single-node file var nodeInfo = new NodeInfo(); nodeInfo.title = "Start"; nodeInfo.body = text; nodes.Add(nodeInfo); break; case NodeFormat.JSON: // Parse it as JSON try { nodes = JsonConvert.DeserializeObject <List <NodeInfo> >(text); } catch (JsonReaderException e) { // dialogue.LogErrorMessage("Error parsing Yarn input: " + e.Message); Debug.Log("Error parsing Yarn input: " + e.Message); } break; case NodeFormat.Text: // check for the existence of at least one "---"+newline sentinel, which divides // the headers from the body // we use a regex to match either \r\n or \n line endings if (System.Text.RegularExpressions.Regex.IsMatch(text, "---.?\n") == false) { // dialogue.LogErrorMessage("Error parsing input: text appears corrupt (no header sentinel)"); Debug.Log("Error parsing input: text appears corrupt (no header sentinel)"); break; } var headerRegex = new System.Text.RegularExpressions.Regex("(?<field>.*): *(?<value>.*)"); var nodeProperties = typeof(NodeInfo).GetProperties(); int lineNumber = 0; using (var reader = new System.IO.StringReader(text)) { string line; while ((line = reader.ReadLine()) != null) { // Create a new node NodeInfo node = new NodeInfo(); // Read header lines do { lineNumber++; // skip empty lines if (line.Length == 0) { continue; } // Attempt to parse the header var headerMatches = headerRegex.Match(line); if (headerMatches == null) { // dialogue.LogErrorMessage(string.Format("Line {0}: Can't parse header '{1}'", lineNumber, line)); Debug.Log(string.Format("Line {0}: Can't parse header '{1}'", lineNumber, line)); continue; } var field = headerMatches.Groups["field"].Value; var value = headerMatches.Groups["value"].Value; // Attempt to set the appropriate property using this field foreach (var property in nodeProperties) { if (property.Name != field) { continue; } // skip properties that can't be written to if (property.CanWrite == false) { continue; } try { var propertyType = property.PropertyType; object convertedValue; if (propertyType.IsAssignableFrom(typeof(string))) { convertedValue = value; } else if (propertyType.IsAssignableFrom(typeof(int))) { convertedValue = int.Parse(value); } else if (propertyType.IsAssignableFrom(typeof(NodeInfo.Position))) { var components = value.Split(','); // we expect 2 components: x and y if (components.Length != 2) { throw new FormatException(); } var position = new NodeInfo.Position(); position.x = int.Parse(components[0]); position.y = int.Parse(components[1]); convertedValue = position; } else { throw new NotSupportedException(); } // we need to box this because structs are value types, // so calling SetValue using 'node' would just modify a copy of 'node' object box = node; property.SetValue(box, convertedValue, null); node = (NodeInfo)box; break; } catch (FormatException) { // dialogue.LogErrorMessage(string.Format("{0}: Error setting '{1}': invalid value '{2}'", lineNumber, field, value)); Debug.Log(string.Format("{0}: Error setting '{1}': invalid value '{2}'", lineNumber, field, value)); } catch (NotSupportedException) { // dialogue.LogErrorMessage(string.Format("{0}: Error setting '{1}': This property cannot be set", lineNumber, field)); Debug.Log(string.Format("{0}: Error setting '{1}': This property cannot be set", lineNumber, field)); } } } while ((line = reader.ReadLine()) != "---"); lineNumber++; // We're past the header; read the body var lines = new List <string>(); // Read header lines until we hit the end of node sentinel or the end of the file while ((line = reader.ReadLine()) != "===" && line != null) { lineNumber++; lines.Add(line); } // We're done reading the lines! Zip 'em up into a string and // store it in the body node.body = string.Join("\n", lines.ToArray()); // And add this node to the list nodes.Add(node); // And now we're ready to move on to the next line! } } break; default: throw new InvalidOperationException("Unknown format " + format.ToString()); } // hooray we're done return(nodes.ToArray()); }