diff --git a/.gitignore b/.gitignore index 4a64d6be..2e8b8bf7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +/.vs/ Backup/ bin/ obj/ diff --git a/nuget.config b/nuget.config index 57b36f04..f5cdc0be 100644 --- a/nuget.config +++ b/nuget.config @@ -1,3 +1,4 @@ + diff --git a/shadowsocks-csharp/3rd/SimpleJson.cs b/shadowsocks-csharp/3rd/SimpleJson.cs deleted file mode 100644 index 6581e84b..00000000 --- a/shadowsocks-csharp/3rd/SimpleJson.cs +++ /dev/null @@ -1,1883 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) 2011, The Outercurve Foundation. -// -// Licensed under the MIT License (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.opensource.org/licenses/mit-license.php -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me) -// https://github.com/facebook-csharp-sdk/simple-json -//----------------------------------------------------------------------- - -// VERSION: - -// NOTE: uncomment the following line to make SimpleJson class internal. -//#define SIMPLE_JSON_INTERNAL - -// NOTE: uncomment the following line to make JsonArray and JsonObject class internal. -//#define SIMPLE_JSON_OBJARRAYINTERNAL - -// NOTE: uncomment the following line to enable dynamic support. -//#define SIMPLE_JSON_DYNAMIC - -// NOTE: uncomment the following line to enable DataContract support. -//#define SIMPLE_JSON_DATACONTRACT - -// NOTE: uncomment the following line to use Reflection.Emit (better performance) instead of method.invoke(). -// don't enable ReflectionEmit for WinRT, Silverlight and WP7. -//#define SIMPLE_JSON_REFLECTIONEMIT - -// NOTE: uncomment the following line if you are compiling under Window Metro style application/library. -// usually already defined in properties -//#define NETFX_CORE; - -// original json parsing code from http://techblog.procurios.nl/k/618/news/view/14605/14863/How-do-I-write-my-own-parser-for-JSON.html - -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -#if SIMPLE_JSON_DYNAMIC -using System.Dynamic; -#endif -using System.Globalization; -using System.Reflection; -#if SIMPLE_JSON_REFLECTIONEMIT -using System.Reflection.Emit; -#endif -#if SIMPLE_JSON_DATACONTRACT -using System.Runtime.Serialization; -#endif -using System.Text; -using SimpleJson.Reflection; - -namespace SimpleJson -{ - #region JsonArray - - /// - /// Represents the json array. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -#if SIMPLE_JSON_OBJARRAYINTERNAL - internal -#else - public -#endif - class JsonArray : List - { - /// - /// Initializes a new instance of the class. - /// - public JsonArray() { } - - /// - /// Initializes a new instance of the class. - /// - /// The capacity of the json array. - public JsonArray(int capacity) : base(capacity) { } - - /// - /// The json representation of the array. - /// - /// The json representation of the array. - public override string ToString() - { - return SimpleJson.SerializeObject(this) ?? string.Empty; - } - } - - #endregion - - #region JsonObject - - /// - /// Represents the json object. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix")] -#if SIMPLE_JSON_OBJARRAYINTERNAL - internal -#else - public -#endif - class JsonObject : -#if SIMPLE_JSON_DYNAMIC - DynamicObject, -#endif - IDictionary - { - /// - /// The internal member dictionary. - /// - private readonly Dictionary _members = new Dictionary(); - - /// - /// Gets the at the specified index. - /// - /// - public object this[int index] - { - get { return GetAtIndex(_members, index); } - } - - internal static object GetAtIndex(IDictionary obj, int index) - { - if (obj == null) - throw new ArgumentNullException("obj"); - - if (index >= obj.Count) - throw new ArgumentOutOfRangeException("index"); - - int i = 0; - foreach (KeyValuePair o in obj) - if (i++ == index) return o.Value; - - return null; - } - - /// - /// Adds the specified key. - /// - /// The key. - /// The value. - public void Add(string key, object value) - { - _members.Add(key, value); - } - - /// - /// Determines whether the specified key contains key. - /// - /// The key. - /// - /// true if the specified key contains key; otherwise, false. - /// - public bool ContainsKey(string key) - { - return _members.ContainsKey(key); - } - - /// - /// Gets the keys. - /// - /// The keys. - public ICollection Keys - { - get { return _members.Keys; } - } - - /// - /// Removes the specified key. - /// - /// The key. - /// - public bool Remove(string key) - { - return _members.Remove(key); - } - - /// - /// Tries the get value. - /// - /// The key. - /// The value. - /// - public bool TryGetValue(string key, out object value) - { - return _members.TryGetValue(key, out value); - } - - /// - /// Gets the values. - /// - /// The values. - public ICollection Values - { - get { return _members.Values; } - } - - /// - /// Gets or sets the with the specified key. - /// - /// - public object this[string key] - { - get { return _members[key]; } - set { _members[key] = value; } - } - - /// - /// Adds the specified item. - /// - /// The item. - public void Add(KeyValuePair item) - { - _members.Add(item.Key, item.Value); - } - - /// - /// Clears this instance. - /// - public void Clear() - { - _members.Clear(); - } - - /// - /// Determines whether [contains] [the specified item]. - /// - /// The item. - /// - /// true if [contains] [the specified item]; otherwise, false. - /// - public bool Contains(KeyValuePair item) - { - return _members.ContainsKey(item.Key) && _members[item.Key] == item.Value; - } - - /// - /// Copies to. - /// - /// The array. - /// Index of the array. - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - int num = Count; - foreach (KeyValuePair kvp in this) - { - array[arrayIndex++] = kvp; - - if (--num <= 0) - return; - } - } - - /// - /// Gets the count. - /// - /// The count. - public int Count - { - get { return _members.Count; } - } - - /// - /// Gets a value indicating whether this instance is read only. - /// - /// - /// true if this instance is read only; otherwise, false. - /// - public bool IsReadOnly - { - get { return false; } - } - - /// - /// Removes the specified item. - /// - /// The item. - /// - public bool Remove(KeyValuePair item) - { - return _members.Remove(item.Key); - } - - /// - /// Gets the enumerator. - /// - /// - public IEnumerator> GetEnumerator() - { - return _members.GetEnumerator(); - } - - /// - /// Returns an enumerator that iterates through a collection. - /// - /// - /// An object that can be used to iterate through the collection. - /// - IEnumerator IEnumerable.GetEnumerator() - { - return _members.GetEnumerator(); - } - - /// - /// Returns a json that represents the current . - /// - /// - /// A json that represents the current . - /// - public override string ToString() - { - return SimpleJson.SerializeObject(this); - } - -#if SIMPLE_JSON_DYNAMIC - /// - /// Provides implementation for type conversion operations. Classes derived from the class can override this method to specify dynamic behavior for operations that convert an object from one type to another. - /// - /// Provides information about the conversion operation. The binder.Type property provides the type to which the object must be converted. For example, for the statement (String)sampleObject in C# (CType(sampleObject, Type) in Visual Basic), where sampleObject is an instance of the class derived from the class, binder.Type returns the type. The binder.Explicit property provides information about the kind of conversion that occurs. It returns true for explicit conversion and false for implicit conversion. - /// The result of the type conversion operation. - /// - /// Alwasy returns true. - /// - public override bool TryConvert(ConvertBinder binder, out object result) - { - // - if (binder == (ConvertBinder)null) - throw new ArgumentNullException("binder"); - // - Type targetType = binder.Type; - - if ((targetType == typeof(IEnumerable)) || - (targetType == typeof(IEnumerable>)) || - (targetType == typeof(IDictionary)) || -#if NETFX_CORE - (targetType == typeof(IDictionary<,>)) -#else - (targetType == typeof(IDictionary)) -#endif -) - { - result = this; - return true; - } - - return base.TryConvert(binder, out result); - } - - /// - /// Provides the implementation for operations that delete an object member. This method is not intended for use in C# or Visual Basic. - /// - /// Provides information about the deletion. - /// - /// Alwasy returns true. - /// - public override bool TryDeleteMember(DeleteMemberBinder binder) - { - // - if (binder == (DeleteMemberBinder)null) - throw new ArgumentNullException("binder"); - // - return _members.Remove(binder.Name); - } - - /// - /// Provides the implementation for operations that get a value by index. Classes derived from the class can override this method to specify dynamic behavior for indexing operations. - /// - /// Provides information about the operation. - /// The indexes that are used in the operation. For example, for the sampleObject[3] operation in C# (sampleObject(3) in Visual Basic), where sampleObject is derived from the DynamicObject class, is equal to 3. - /// The result of the index operation. - /// - /// Alwasy returns true. - /// - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - if (indexes.Length == 1) - { - result = ((IDictionary)this)[(string)indexes[0]]; - return true; - } - result = (object)null; - return true; - } - - /// - /// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property. - /// - /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. - /// The result of the get operation. For example, if the method is called for a property, you can assign the property value to . - /// - /// Alwasy returns true. - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - object value; - if (_members.TryGetValue(binder.Name, out value)) - { - result = value; - return true; - } - result = (object)null; - return true; - } - - /// - /// Provides the implementation for operations that set a value by index. Classes derived from the class can override this method to specify dynamic behavior for operations that access objects by a specified index. - /// - /// Provides information about the operation. - /// The indexes that are used in the operation. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 3. - /// The value to set to the object that has the specified index. For example, for the sampleObject[3] = 10 operation in C# (sampleObject(3) = 10 in Visual Basic), where sampleObject is derived from the class, is equal to 10. - /// - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown. - /// - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) - { - if (indexes.Length == 1) - { - ((IDictionary)this)[(string)indexes[0]] = value; - return true; - } - - return base.TrySetIndex(binder, indexes, value); - } - - /// - /// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property. - /// - /// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive. - /// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test". - /// - /// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.) - /// - public override bool TrySetMember(SetMemberBinder binder, object value) - { - // - if (binder == (SetMemberBinder)null) - throw new ArgumentNullException("binder"); - // - _members[binder.Name] = value; - return true; - } - - /// - /// Returns the enumeration of all dynamic member names. - /// - /// - /// A sequence that contains dynamic member names. - /// - public override IEnumerable GetDynamicMemberNames() - { - foreach (var key in Keys) - yield return key; - } -#endif - } - - #endregion -} - -namespace SimpleJson -{ - #region JsonParser - - /// - /// This class encodes and decodes JSON strings. - /// Spec. details, see http://www.json.org/ - /// - /// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). - /// All numbers are parsed to doubles. - /// -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class SimpleJson - { - private const int TOKEN_NONE = 0; - private const int TOKEN_CURLY_OPEN = 1; - private const int TOKEN_CURLY_CLOSE = 2; - private const int TOKEN_SQUARED_OPEN = 3; - private const int TOKEN_SQUARED_CLOSE = 4; - private const int TOKEN_COLON = 5; - private const int TOKEN_COMMA = 6; - private const int TOKEN_STRING = 7; - private const int TOKEN_NUMBER = 8; - private const int TOKEN_TRUE = 9; - private const int TOKEN_FALSE = 10; - private const int TOKEN_NULL = 11; - - private const int BUILDER_CAPACITY = 2000; - - /// - /// Parses the string json into a value - /// - /// A JSON string. - /// An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false - public static object DeserializeObject(string json) - { - object @object; - if (TryDeserializeObject(json, out @object)) - return @object; - throw new System.Runtime.Serialization.SerializationException("Invalid JSON string"); - } - - /// - /// Try parsing the json string into a value. - /// - /// - /// A JSON string. - /// - /// - /// The object. - /// - /// - /// Returns true if successfull otherwise false. - /// - public static bool TryDeserializeObject(string json, out object @object) - { - bool success = true; - if (json != null) - { - char[] charArray = json.ToCharArray(); - int index = 0; - @object = ParseValue(charArray, ref index, ref success); - } - else - @object = null; - - return success; - } - - public static object DeserializeObject(string json, Type type, IJsonSerializerStrategy jsonSerializerStrategy) - { - object jsonObject = DeserializeObject(json); - - return type == null || jsonObject != null && -#if NETFX_CORE - jsonObject.GetType().GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()) -#else - jsonObject.GetType().IsAssignableFrom(type) -#endif - ? jsonObject - : (jsonSerializerStrategy ?? CurrentJsonSerializerStrategy).DeserializeObject(jsonObject, type); - } - - public static object DeserializeObject(string json, Type type) - { - return DeserializeObject(json, type, null); - } - - public static T DeserializeObject(string json, IJsonSerializerStrategy jsonSerializerStrategy) - { - return (T)DeserializeObject(json, typeof(T), jsonSerializerStrategy); - } - - public static T DeserializeObject(string json) - { - return (T)DeserializeObject(json, typeof(T), null); - } - - /// - /// Converts a IDictionary<string,object> / IList<object> object into a JSON string - /// - /// A IDictionary<string,object> / IList<object> - /// Serializer strategy to use - /// A JSON encoded string, or null if object 'json' is not serializable - public static string SerializeObject(object json, IJsonSerializerStrategy jsonSerializerStrategy) - { - StringBuilder builder = new StringBuilder(BUILDER_CAPACITY); - bool success = SerializeValue(jsonSerializerStrategy, json, builder); - return (success ? builder.ToString() : null); - } - - public static string SerializeObject(object json) - { - return SerializeObject(json, CurrentJsonSerializerStrategy); - } - - protected static IDictionary ParseObject(char[] json, ref int index, ref bool success) - { - IDictionary table = new JsonObject(); - int token; - - // { - NextToken(json, ref index); - - bool done = false; - while (!done) - { - token = LookAhead(json, index); - if (token == TOKEN_NONE) - { - success = false; - return null; - } - else if (token == TOKEN_COMMA) - NextToken(json, ref index); - else if (token == TOKEN_CURLY_CLOSE) - { - NextToken(json, ref index); - return table; - } - else - { - // name - string name = ParseString(json, ref index, ref success); - if (!success) - { - success = false; - return null; - } - - // : - token = NextToken(json, ref index); - if (token != TOKEN_COLON) - { - success = false; - return null; - } - - // value - object value = ParseValue(json, ref index, ref success); - if (!success) - { - success = false; - return null; - } - - table[name] = value; - } - } - - return table; - } - - protected static JsonArray ParseArray(char[] json, ref int index, ref bool success) - { - JsonArray array = new JsonArray(); - - // [ - NextToken(json, ref index); - - bool done = false; - while (!done) - { - int token = LookAhead(json, index); - if (token == TOKEN_NONE) - { - success = false; - return null; - } - else if (token == TOKEN_COMMA) - NextToken(json, ref index); - else if (token == TOKEN_SQUARED_CLOSE) - { - NextToken(json, ref index); - break; - } - else - { - object value = ParseValue(json, ref index, ref success); - if (!success) - return null; - array.Add(value); - } - } - - return array; - } - - protected static object ParseValue(char[] json, ref int index, ref bool success) - { - switch (LookAhead(json, index)) - { - case TOKEN_STRING: - return ParseString(json, ref index, ref success); - case TOKEN_NUMBER: - return ParseNumber(json, ref index, ref success); - case TOKEN_CURLY_OPEN: - return ParseObject(json, ref index, ref success); - case TOKEN_SQUARED_OPEN: - return ParseArray(json, ref index, ref success); - case TOKEN_TRUE: - NextToken(json, ref index); - return true; - case TOKEN_FALSE: - NextToken(json, ref index); - return false; - case TOKEN_NULL: - NextToken(json, ref index); - return null; - case TOKEN_NONE: - break; - } - - success = false; - return null; - } - - protected static string ParseString(char[] json, ref int index, ref bool success) - { - StringBuilder s = new StringBuilder(BUILDER_CAPACITY); - char c; - - EatWhitespace(json, ref index); - - // " - c = json[index++]; - - bool complete = false; - while (!complete) - { - if (index == json.Length) - { - break; - } - - c = json[index++]; - if (c == '"') - { - complete = true; - break; - } - else if (c == '\\') - { - if (index == json.Length) - break; - c = json[index++]; - if (c == '"') - s.Append('"'); - else if (c == '\\') - s.Append('\\'); - else if (c == '/') - s.Append('/'); - else if (c == 'b') - s.Append('\b'); - else if (c == 'f') - s.Append('\f'); - else if (c == 'n') - s.Append('\n'); - else if (c == 'r') - s.Append('\r'); - else if (c == 't') - s.Append('\t'); - else if (c == 'u') - { - int remainingLength = json.Length - index; - if (remainingLength >= 4) - { - // parse the 32 bit hex into an integer codepoint - uint codePoint; - if ( - !(success = - UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, - CultureInfo.InvariantCulture, out codePoint))) - return ""; - - // convert the integer codepoint to a unicode char and add to string - - if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate - { - index += 4; // skip 4 chars - remainingLength = json.Length - index; - if (remainingLength >= 6) - { - uint lowCodePoint; - if (new string(json, index, 2) == "\\u" && - UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, - CultureInfo.InvariantCulture, out lowCodePoint)) - { - if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate - { - s.Append((char)codePoint); - s.Append((char)lowCodePoint); - index += 6; // skip 6 chars - continue; - } - } - } - success = false; // invalid surrogate pair - return ""; - } -#if SILVERLIGHT - s.Append(ConvertFromUtf32((int)codePoint)); -#else - s.Append(Char.ConvertFromUtf32((int)codePoint)); -#endif - // skip 4 chars - index += 4; - } - else - break; - } - } - else - s.Append(c); - } - - if (!complete) - { - success = false; - return null; - } - - return s.ToString(); - } - -#if SILVERLIGHT - private static string ConvertFromUtf32(int utf32) - { - // http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm - if (utf32 < 0 || utf32 > 0x10FFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); - if (0xD800 <= utf32 && utf32 <= 0xDFFF) - throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); - if (utf32 < 0x10000) - return new string((char)utf32, 1); - utf32 -= 0x10000; - return new string(new char[] {(char) ((utf32 >> 10) + 0xD800),(char) (utf32 % 0x0400 + 0xDC00)}); - } -#endif - - protected static object ParseNumber(char[] json, ref int index, ref bool success) - { - EatWhitespace(json, ref index); - - int lastIndex = GetLastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - - object returnNumber; - string str = new string(json, index, charLength); - if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) - { - double number; - success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); - returnNumber = number; - } - else - { - long number; - success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); - returnNumber = number; - } - - index = lastIndex + 1; - return returnNumber; - } - - protected static int GetLastIndexOfNumber(char[] json, int index) - { - int lastIndex; - - for (lastIndex = index; lastIndex < json.Length; lastIndex++) - if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; - return lastIndex - 1; - } - - protected static void EatWhitespace(char[] json, ref int index) - { - for (; index < json.Length; index++) - if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break; - } - - protected static int LookAhead(char[] json, int index) - { - int saveIndex = index; - return NextToken(json, ref saveIndex); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - protected static int NextToken(char[] json, ref int index) - { - EatWhitespace(json, ref index); - - if (index == json.Length) - return TOKEN_NONE; - - char c = json[index]; - index++; - switch (c) - { - case '{': - return TOKEN_CURLY_OPEN; - case '}': - return TOKEN_CURLY_CLOSE; - case '[': - return TOKEN_SQUARED_OPEN; - case ']': - return TOKEN_SQUARED_CLOSE; - case ',': - return TOKEN_COMMA; - case '"': - return TOKEN_STRING; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return TOKEN_NUMBER; - case ':': - return TOKEN_COLON; - } - index--; - - int remainingLength = json.Length - index; - - // false - if (remainingLength >= 5) - { - if (json[index] == 'f' && - json[index + 1] == 'a' && - json[index + 2] == 'l' && - json[index + 3] == 's' && - json[index + 4] == 'e') - { - index += 5; - return TOKEN_FALSE; - } - } - - // true - if (remainingLength >= 4) - { - if (json[index] == 't' && - json[index + 1] == 'r' && - json[index + 2] == 'u' && - json[index + 3] == 'e') - { - index += 4; - return TOKEN_TRUE; - } - } - - // null - if (remainingLength >= 4) - { - if (json[index] == 'n' && - json[index + 1] == 'u' && - json[index + 2] == 'l' && - json[index + 3] == 'l') - { - index += 4; - return TOKEN_NULL; - } - } - - return TOKEN_NONE; - } - - protected static bool SerializeValue(IJsonSerializerStrategy jsonSerializerStrategy, object value, StringBuilder builder) - { - bool success = true; - - if (value is string) - success = SerializeString((string)value, builder); - else if (value is IDictionary) - { - IDictionary dict = (IDictionary)value; - success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); - } - else if (value is IDictionary) - { - IDictionary dict = (IDictionary)value; - success = SerializeObject(jsonSerializerStrategy, dict.Keys, dict.Values, builder); - } - else if (value is IEnumerable) - success = SerializeArray(jsonSerializerStrategy, (IEnumerable)value, builder); - else if (IsNumeric(value)) - success = SerializeNumber(value, builder); - else if (value is Boolean) - builder.Append((bool)value ? "true" : "false"); - else if (value == null) - builder.Append("null"); - else - { - object serializedObject; - success = jsonSerializerStrategy.SerializeNonPrimitiveObject(value, out serializedObject); - if (success) - SerializeValue(jsonSerializerStrategy, serializedObject, builder); - } - - return success; - } - - protected static bool SerializeObject(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable keys, IEnumerable values, StringBuilder builder) - { - builder.Append("{\r\n"); - - IEnumerator ke = keys.GetEnumerator(); - IEnumerator ve = values.GetEnumerator(); - - bool first = true; - while (ke.MoveNext() && ve.MoveNext()) - { - object key = ke.Current; - object value = ve.Current; - - if (!first) - builder.Append(",\r\n"); - - if (key is string) - SerializeString((string)key, builder); - else - if (!SerializeValue(jsonSerializerStrategy, value, builder)) return false; - - builder.Append(" : "); - if (!SerializeValue(jsonSerializerStrategy, value, builder)) - return false; - - first = false; - } - - builder.Append("}\r\n"); - return true; - } - - protected static bool SerializeArray(IJsonSerializerStrategy jsonSerializerStrategy, IEnumerable anArray, StringBuilder builder) - { - builder.Append("[\r\n "); - - bool first = true; - foreach (object value in anArray) - { - if (!first) - builder.Append(",\r\n "); - - if (!SerializeValue(jsonSerializerStrategy, value, builder)) - return false; - - first = false; - } - - builder.Append("\r\n]"); - return true; - } - - protected static bool SerializeString(string aString, StringBuilder builder) - { - builder.Append("\""); - - char[] charArray = aString.ToCharArray(); - for (int i = 0; i < charArray.Length; i++) - { - char c = charArray[i]; - if (c == '"') - builder.Append("\\\""); - else if (c == '\\') - builder.Append("\\\\"); - else if (c == '\b') - builder.Append("\\b"); - else if (c == '\f') - builder.Append("\\f"); - else if (c == '\n') - builder.Append("\\n"); - else if (c == '\r') - builder.Append("\\r"); - else if (c == '\t') - builder.Append("\\t"); - else - builder.Append(c); - } - - builder.Append("\""); - return true; - } - - protected static bool SerializeNumber(object number, StringBuilder builder) - { - if (number is long) - { - builder.Append(((long)number).ToString(CultureInfo.InvariantCulture)); - } - else if (number is ulong) - { - builder.Append(((ulong)number).ToString(CultureInfo.InvariantCulture)); - } - else if (number is int) - { - builder.Append(((int)number).ToString(CultureInfo.InvariantCulture)); - } - else if (number is uint) - { - builder.Append(((uint)number).ToString(CultureInfo.InvariantCulture)); - } - else if (number is decimal) - { - builder.Append(((decimal)number).ToString(CultureInfo.InvariantCulture)); - } - else if (number is float) - { - builder.Append(((float)number).ToString(CultureInfo.InvariantCulture)); - } - else - { - builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); - } - - return true; - } - - /// - /// Determines if a given object is numeric in any way - /// (can be integer, double, null, etc). - /// - protected static bool IsNumeric(object value) - { - if (value is sbyte) return true; - if (value is byte) return true; - if (value is short) return true; - if (value is ushort) return true; - if (value is int) return true; - if (value is uint) return true; - if (value is long) return true; - if (value is ulong) return true; - if (value is float) return true; - if (value is double) return true; - if (value is decimal) return true; - return false; - } - - private static IJsonSerializerStrategy currentJsonSerializerStrategy; - public static IJsonSerializerStrategy CurrentJsonSerializerStrategy - { - get - { - // todo: implement locking mechanism. - return currentJsonSerializerStrategy ?? - (currentJsonSerializerStrategy = -#if SIMPLE_JSON_DATACONTRACT - DataContractJsonSerializerStrategy -#else - PocoJsonSerializerStrategy -#endif -); - } - - set - { - currentJsonSerializerStrategy = value; - } - } - - private static PocoJsonSerializerStrategy pocoJsonSerializerStrategy; - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] - public static PocoJsonSerializerStrategy PocoJsonSerializerStrategy - { - get - { - // todo: implement locking mechanism. - return pocoJsonSerializerStrategy ?? (pocoJsonSerializerStrategy = new PocoJsonSerializerStrategy()); - } - } - -#if SIMPLE_JSON_DATACONTRACT - - private static DataContractJsonSerializerStrategy dataContractJsonSerializerStrategy; - [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Advanced)] - public static DataContractJsonSerializerStrategy DataContractJsonSerializerStrategy - { - get - { - // todo: implement locking mechanism. - return dataContractJsonSerializerStrategy ?? (dataContractJsonSerializerStrategy = new DataContractJsonSerializerStrategy()); - } - } - -#endif - } - - #endregion - - #region Simple Json Serializer Strategies - -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - interface IJsonSerializerStrategy - { - bool SerializeNonPrimitiveObject(object input, out object output); - - object DeserializeObject(object value, Type type); - } - -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class PocoJsonSerializerStrategy : IJsonSerializerStrategy - { - internal CacheResolver CacheResolver; - - private static readonly string[] Iso8601Format = new string[] - { - @"yyyy-MM-dd\THH:mm:ss.FFFFFFF\Z", - @"yyyy-MM-dd\THH:mm:ss\Z", - @"yyyy-MM-dd\THH:mm:ssK" - }; - - public PocoJsonSerializerStrategy() - { - CacheResolver = new CacheResolver(BuildMap); - } - - protected virtual void BuildMap(Type type, SafeDictionary memberMaps) - { -#if NETFX_CORE - foreach (PropertyInfo info in type.GetTypeInfo().DeclaredProperties) { - var getMethod = info.GetMethod; - if(getMethod==null || !getMethod.IsPublic || getMethod.IsStatic) continue; -#else - foreach (PropertyInfo info in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { -#endif - memberMaps.Add(info.Name, new CacheResolver.MemberMap(info)); - } -#if NETFX_CORE - foreach (FieldInfo info in type.GetTypeInfo().DeclaredFields) { - if(!info.IsPublic || info.IsStatic) continue; -#else - foreach (FieldInfo info in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) - { -#endif - memberMaps.Add(info.Name, new CacheResolver.MemberMap(info)); - } - } - - public virtual bool SerializeNonPrimitiveObject(object input, out object output) - { - return TrySerializeKnownTypes(input, out output) || TrySerializeUnknownTypes(input, out output); - } - - [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] - public virtual object DeserializeObject(object value, Type type) - { - object obj = null; - if (value is string) - { - string str = value as string; - - if (!string.IsNullOrEmpty(str)) - { - if (type == typeof(DateTime) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(DateTime))) - obj = DateTime.ParseExact(str, Iso8601Format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal); - else if (type == typeof(Guid) || (ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid))) - obj = new Guid(str); - else - obj = str; - } - else - { - if (type == typeof(Guid)) - obj= default(Guid); - else if(ReflectionUtils.IsNullableType(type) && Nullable.GetUnderlyingType(type) == typeof(Guid)) - obj = null; - else - obj = str; - } - } - else if (value is bool) - obj = value; - else if (value == null) - obj = null; - else if ((value is long && type == typeof(long)) || (value is double && type == typeof(double))) - obj = value; - else if ((value is double && type != typeof(double)) || (value is long && type != typeof(long))) - { - obj = -#if NETFX_CORE - type == typeof(int) || type == typeof(long) || type == typeof(double) ||type == typeof(float) || type == typeof(bool) || type == typeof(decimal) ||type == typeof(byte) || type == typeof(short) -#else - typeof(IConvertible).IsAssignableFrom(type) -#endif - ? Convert.ChangeType(value, type, CultureInfo.InvariantCulture) : value; - } - else - { - if (value is IDictionary) - { - IDictionary jsonObject = (IDictionary)value; - - if (ReflectionUtils.IsTypeDictionary(type)) - { - // if dictionary then -#if NETFX_CORE - Type keyType = type.GetTypeInfo().GenericTypeArguments[0]; - Type valueType = type.GetTypeInfo().GenericTypeArguments[1]; -#else - Type keyType = type.GetGenericArguments()[0]; - Type valueType = type.GetGenericArguments()[1]; -#endif - - Type genericType = typeof(Dictionary<,>).MakeGenericType(keyType, valueType); - -#if NETFX_CORE - dynamic dict = CacheResolver.GetNewInstance(genericType); -#else - IDictionary dict = (IDictionary)CacheResolver.GetNewInstance(genericType); -#endif - foreach (KeyValuePair kvp in jsonObject) - { - dict.Add(kvp.Key, DeserializeObject(kvp.Value, valueType)); - } - - obj = dict; - } - else - { - obj = CacheResolver.GetNewInstance(type); - SafeDictionary maps = CacheResolver.LoadMaps(type); - - if (maps == null) - { - obj = value; - } - else - { - foreach (KeyValuePair keyValuePair in maps) - { - CacheResolver.MemberMap v = keyValuePair.Value; - if (v.Setter == null) - continue; - - string jsonKey = keyValuePair.Key; - if (jsonObject.ContainsKey(jsonKey)) - { - object jsonValue = DeserializeObject(jsonObject[jsonKey], v.Type); - v.Setter(obj, jsonValue); - } - } - } - } - } - else if (value is IList) - { - IList jsonObject = (IList)value; - IList list = null; - - if (type.IsArray) - { - list = (IList)Activator.CreateInstance(type, jsonObject.Count); - int i = 0; - foreach (object o in jsonObject) - list[i++] = DeserializeObject(o, type.GetElementType()); - } - else if (ReflectionUtils.IsTypeGenericeCollectionInterface(type) || -#if NETFX_CORE - typeof(IList).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo()) -#else - typeof(IList).IsAssignableFrom(type) -#endif -) - { -#if NETFX_CORE - Type innerType = type.GetTypeInfo().GenericTypeArguments[0]; -#else - Type innerType = type.GetGenericArguments()[0]; -#endif - Type genericType = typeof(List<>).MakeGenericType(innerType); - list = (IList)CacheResolver.GetNewInstance(genericType); - foreach (object o in jsonObject) - list.Add(DeserializeObject(o, innerType)); - } - - obj = list; - } - - return obj; - } - - if (ReflectionUtils.IsNullableType(type)) - return ReflectionUtils.ToNullableType(obj, type); - - if (obj == null) - { - if (type == typeof(Guid)) - return default(Guid); - } - - return obj; - } - - protected virtual object SerializeEnum(Enum p) - { - return Convert.ToDouble(p, CultureInfo.InvariantCulture); - } - - protected virtual bool TrySerializeKnownTypes(object input, out object output) - { - bool returnValue = true; - if (input is DateTime) - output = ((DateTime)input).ToUniversalTime().ToString(Iso8601Format[0], CultureInfo.InvariantCulture); - else if (input is Guid) - output = ((Guid)input).ToString("D"); - else if (input is Uri) - output = input.ToString(); - else if (input is Enum) - output = SerializeEnum((Enum)input); - else - { - returnValue = false; - output = null; - } - - return returnValue; - } - - protected virtual bool TrySerializeUnknownTypes(object input, out object output) - { - output = null; - - // todo: implement caching for types - Type type = input.GetType(); - - if (type.FullName == null) - return false; - - IDictionary obj = new JsonObject(); - - SafeDictionary maps = CacheResolver.LoadMaps(type); - - foreach (KeyValuePair keyValuePair in maps) - { - if (keyValuePair.Value.Getter != null) - obj.Add(keyValuePair.Key, keyValuePair.Value.Getter(input)); - } - - output = obj; - return true; - } - } - -#if SIMPLE_JSON_DATACONTRACT -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class DataContractJsonSerializerStrategy : PocoJsonSerializerStrategy - { - public DataContractJsonSerializerStrategy() - { - CacheResolver = new CacheResolver(BuildMap); - } - - protected override void BuildMap(Type type, SafeDictionary map) - { - bool hasDataContract = ReflectionUtils.GetAttribute(type, typeof(DataContractAttribute)) != null; - if (!hasDataContract) - { - base.BuildMap(type, map); - return; - } - - string jsonKey; -#if NETFX_CORE - foreach (PropertyInfo info in type.GetTypeInfo().DeclaredProperties) -#else - foreach (PropertyInfo info in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) -#endif - { - if (CanAdd(info, out jsonKey)) - map.Add(jsonKey, new CacheResolver.MemberMap(info)); - } - -#if NETFX_CORE - foreach (FieldInfo info in type.GetTypeInfo().DeclaredFields) -#else - foreach (FieldInfo info in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) -#endif - { - if (CanAdd(info, out jsonKey)) - map.Add(jsonKey, new CacheResolver.MemberMap(info)); - } - - // todo implement sorting for DATACONTRACT. - } - - private static bool CanAdd(MemberInfo info, out string jsonKey) - { - jsonKey = null; - - if (ReflectionUtils.GetAttribute(info, typeof(IgnoreDataMemberAttribute)) != null) - return false; - - DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)ReflectionUtils.GetAttribute(info, typeof(DataMemberAttribute)); - - if (dataMemberAttribute == null) - return false; - - jsonKey = string.IsNullOrEmpty(dataMemberAttribute.Name) ? info.Name : dataMemberAttribute.Name; - return true; - } - } -#endif - - #endregion - - #region Reflection helpers - - namespace Reflection - { -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class ReflectionUtils - { - public static Attribute GetAttribute(MemberInfo info, Type type) - { -#if NETFX_CORE - if (info == null || type == null || !info.IsDefined(type)) - return null; - return info.GetCustomAttribute(type); -#else - if (info == null || type == null || !Attribute.IsDefined(info, type)) - return null; - return Attribute.GetCustomAttribute(info, type); -#endif - } - - public static Attribute GetAttribute(Type objectType, Type attributeType) - { - -#if NETFX_CORE - if (objectType == null || attributeType == null || !objectType.GetTypeInfo().IsDefined(attributeType)) - return null; - return objectType.GetTypeInfo().GetCustomAttribute(attributeType); -#else - if (objectType == null || attributeType == null || !Attribute.IsDefined(objectType, attributeType)) - return null; - return Attribute.GetCustomAttribute(objectType, attributeType); -#endif - } - - public static bool IsTypeGenericeCollectionInterface(Type type) - { -#if NETFX_CORE - if (!type.GetTypeInfo().IsGenericType) -#else - if (!type.IsGenericType) -#endif - return false; - - Type genericDefinition = type.GetGenericTypeDefinition(); - - return (genericDefinition == typeof(IList<>) || genericDefinition == typeof(ICollection<>) || genericDefinition == typeof(IEnumerable<>)); - } - - public static bool IsTypeDictionary(Type type) - { -#if NETFX_CORE - if (typeof(IDictionary<,>).GetTypeInfo().IsAssignableFrom(type.GetTypeInfo())) - return true; - - if (!type.GetTypeInfo().IsGenericType) - return false; -#else - if (typeof(IDictionary).IsAssignableFrom(type)) - return true; - - if (!type.IsGenericType) - return false; -#endif - Type genericDefinition = type.GetGenericTypeDefinition(); - return genericDefinition == typeof(IDictionary<,>); - } - - public static bool IsNullableType(Type type) - { - return -#if NETFX_CORE - type.GetTypeInfo().IsGenericType -#else - type.IsGenericType -#endif - && type.GetGenericTypeDefinition() == typeof(Nullable<>); - } - - public static object ToNullableType(object obj, Type nullableType) - { - return obj == null ? null : Convert.ChangeType(obj, Nullable.GetUnderlyingType(nullableType), CultureInfo.InvariantCulture); - } - } - -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - delegate object GetHandler(object source); - -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - delegate void SetHandler(object source, object value); - -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - delegate void MemberMapLoader(Type type, SafeDictionary memberMaps); - -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class CacheResolver - { - private readonly MemberMapLoader _memberMapLoader; - private readonly SafeDictionary> _memberMapsCache = new SafeDictionary>(); - - delegate object CtorDelegate(); - readonly static SafeDictionary ConstructorCache = new SafeDictionary(); - - public CacheResolver(MemberMapLoader memberMapLoader) - { - _memberMapLoader = memberMapLoader; - } - - [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")] - public static object GetNewInstance(Type type) - { - CtorDelegate c; - if (ConstructorCache.TryGetValue(type, out c)) - return c(); -#if SIMPLE_JSON_REFLECTIONEMIT - DynamicMethod dynamicMethod = new DynamicMethod("Create" + type.FullName, typeof(object), Type.EmptyTypes, type, true); - dynamicMethod.InitLocals = true; - ILGenerator generator = dynamicMethod.GetILGenerator(); - if (type.IsValueType) - { - generator.DeclareLocal(type); - generator.Emit(OpCodes.Ldloc_0); - generator.Emit(OpCodes.Box, type); - } - else - { - ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); - if (constructorInfo == null) - throw new Exception(string.Format(CultureInfo.InvariantCulture, "Could not get constructor for {0}.", type)); - generator.Emit(OpCodes.Newobj, constructorInfo); - } - generator.Emit(OpCodes.Ret); - c = (CtorDelegate)dynamicMethod.CreateDelegate(typeof(CtorDelegate)); - ConstructorCache.Add(type, c); - return c(); -#else -#if NETFX_CORE - IEnumerable constructorInfos = type.GetTypeInfo().DeclaredConstructors; - ConstructorInfo constructorInfo = null; - foreach (ConstructorInfo item in constructorInfos) // FirstOrDefault() - { - if (item.GetParameters().Length == 0) // Default ctor - make sure it doesn't contain any parameters - { - constructorInfo = item; - break; - } - } -#else - ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null); -#endif - c = delegate { return constructorInfo.Invoke(null); }; - ConstructorCache.Add(type, c); - return c(); -#endif - } - - public SafeDictionary LoadMaps(Type type) - { - if (type == null || type == typeof(object)) - return null; - SafeDictionary maps; - if (_memberMapsCache.TryGetValue(type, out maps)) - return maps; - maps = new SafeDictionary(); - _memberMapLoader(type, maps); - _memberMapsCache.Add(type, maps); - return maps; - } - -#if SIMPLE_JSON_REFLECTIONEMIT - static DynamicMethod CreateDynamicMethod(string name, Type returnType, Type[] parameterTypes, Type owner) - { - DynamicMethod dynamicMethod = !owner.IsInterface - ? new DynamicMethod(name, returnType, parameterTypes, owner, true) - : new DynamicMethod(name, returnType, parameterTypes, (Module)null, true); - - return dynamicMethod; - } -#endif - - static GetHandler CreateGetHandler(FieldInfo fieldInfo) - { -#if SIMPLE_JSON_REFLECTIONEMIT - Type type = fieldInfo.FieldType; - DynamicMethod dynamicGet = CreateDynamicMethod("Get" + fieldInfo.Name, fieldInfo.DeclaringType, new Type[] { typeof(object) }, fieldInfo.DeclaringType); - ILGenerator getGenerator = dynamicGet.GetILGenerator(); - - getGenerator.Emit(OpCodes.Ldarg_0); - getGenerator.Emit(OpCodes.Ldfld, fieldInfo); - if (type.IsValueType) - getGenerator.Emit(OpCodes.Box, type); - getGenerator.Emit(OpCodes.Ret); - - return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler)); -#else - return delegate(object instance) { return fieldInfo.GetValue(instance); }; -#endif - } - - static SetHandler CreateSetHandler(FieldInfo fieldInfo) - { - if (fieldInfo.IsInitOnly || fieldInfo.IsLiteral) - return null; -#if SIMPLE_JSON_REFLECTIONEMIT - Type type = fieldInfo.FieldType; - DynamicMethod dynamicSet = CreateDynamicMethod("Set" + fieldInfo.Name, null, new Type[] { typeof(object), typeof(object) }, fieldInfo.DeclaringType); - ILGenerator setGenerator = dynamicSet.GetILGenerator(); - - setGenerator.Emit(OpCodes.Ldarg_0); - setGenerator.Emit(OpCodes.Ldarg_1); - if (type.IsValueType) - setGenerator.Emit(OpCodes.Unbox_Any, type); - setGenerator.Emit(OpCodes.Stfld, fieldInfo); - setGenerator.Emit(OpCodes.Ret); - - return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler)); -#else - return delegate(object instance, object value) { fieldInfo.SetValue(instance, value); }; -#endif - } - - static GetHandler CreateGetHandler(PropertyInfo propertyInfo) - { -#if NETFX_CORE - MethodInfo getMethodInfo = propertyInfo.GetMethod; -#else - MethodInfo getMethodInfo = propertyInfo.GetGetMethod(true); -#endif - if (getMethodInfo == null) - return null; -#if SIMPLE_JSON_REFLECTIONEMIT - Type type = propertyInfo.PropertyType; - DynamicMethod dynamicGet = CreateDynamicMethod("Get" + propertyInfo.Name, propertyInfo.DeclaringType, new Type[] { typeof(object) }, propertyInfo.DeclaringType); - ILGenerator getGenerator = dynamicGet.GetILGenerator(); - - getGenerator.Emit(OpCodes.Ldarg_0); - getGenerator.Emit(OpCodes.Call, getMethodInfo); - if (type.IsValueType) - getGenerator.Emit(OpCodes.Box, type); - getGenerator.Emit(OpCodes.Ret); - - return (GetHandler)dynamicGet.CreateDelegate(typeof(GetHandler)); -#else -#if NETFX_CORE - return delegate(object instance) { return getMethodInfo.Invoke(instance, new Type[] { }); }; -#else - return delegate(object instance) { return getMethodInfo.Invoke(instance, Type.EmptyTypes); }; -#endif -#endif - } - - static SetHandler CreateSetHandler(PropertyInfo propertyInfo) - { -#if NETFX_CORE - MethodInfo setMethodInfo = propertyInfo.SetMethod; -#else - MethodInfo setMethodInfo = propertyInfo.GetSetMethod(true); -#endif - if (setMethodInfo == null) - return null; -#if SIMPLE_JSON_REFLECTIONEMIT - Type type = propertyInfo.PropertyType; - DynamicMethod dynamicSet = CreateDynamicMethod("Set" + propertyInfo.Name, null, new Type[] { typeof(object), typeof(object) }, propertyInfo.DeclaringType); - ILGenerator setGenerator = dynamicSet.GetILGenerator(); - - setGenerator.Emit(OpCodes.Ldarg_0); - setGenerator.Emit(OpCodes.Ldarg_1); - if (type.IsValueType) - setGenerator.Emit(OpCodes.Unbox_Any, type); - setGenerator.Emit(OpCodes.Call, setMethodInfo); - setGenerator.Emit(OpCodes.Ret); - return (SetHandler)dynamicSet.CreateDelegate(typeof(SetHandler)); -#else - return delegate(object instance, object value) { setMethodInfo.Invoke(instance, new[] { value }); }; -#endif - } - -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - sealed class MemberMap - { - public readonly MemberInfo MemberInfo; - public readonly Type Type; - public readonly GetHandler Getter; - public readonly SetHandler Setter; - - public MemberMap(PropertyInfo propertyInfo) - { - MemberInfo = propertyInfo; - Type = propertyInfo.PropertyType; - Getter = CreateGetHandler(propertyInfo); - Setter = CreateSetHandler(propertyInfo); - } - - public MemberMap(FieldInfo fieldInfo) - { - MemberInfo = fieldInfo; - Type = fieldInfo.FieldType; - Getter = CreateGetHandler(fieldInfo); - Setter = CreateSetHandler(fieldInfo); - } - } - } - -#if SIMPLE_JSON_INTERNAL - internal -#else - public -#endif - class SafeDictionary - { - private readonly object _padlock = new object(); - private readonly Dictionary _dictionary = new Dictionary(); - - public bool TryGetValue(TKey key, out TValue value) - { - return _dictionary.TryGetValue(key, out value); - } - - public TValue this[TKey key] - { - get { return _dictionary[key]; } - } - - public IEnumerator> GetEnumerator() - { - return ((ICollection>)_dictionary).GetEnumerator(); - } - - public void Add(TKey key, TValue value) - { - lock (_padlock) - { - if (_dictionary.ContainsKey(key) == false) - _dictionary.Add(key, value); - } - } - } - } - - #endregion -} \ No newline at end of file diff --git a/shadowsocks-csharp/Controller/FileManager.cs b/shadowsocks-csharp/Controller/FileManager.cs index cc8f3bf9..f6edf147 100755 --- a/shadowsocks-csharp/Controller/FileManager.cs +++ b/shadowsocks-csharp/Controller/FileManager.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Text; namespace Shadowsocks.Controller { @@ -12,9 +10,7 @@ namespace Shadowsocks.Controller { try { - System.IO.FileStream _FileStream = - new System.IO.FileStream(fileName, System.IO.FileMode.Create, - System.IO.FileAccess.Write); + FileStream _FileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write); _FileStream.Write(content, 0, content.Length); _FileStream.Close(); return true; @@ -31,7 +27,7 @@ namespace Shadowsocks.Controller { FileStream destinationFile = File.Create(fileName); - // Because the uncompressed size of the file is unknown, + // Because the uncompressed size of the file is unknown, // we are using an arbitrary buffer size. byte[] buffer = new byte[4096]; int n; @@ -39,17 +35,13 @@ namespace Shadowsocks.Controller using (GZipStream input = new GZipStream(new MemoryStream(content), CompressionMode.Decompress, false)) { - while (true) + while ((n = input.Read(buffer, 0, buffer.Length)) > 0) { - n = input.Read(buffer, 0, buffer.Length); - if (n == 0) - { - break; - } destinationFile.Write(buffer, 0, n); } } destinationFile.Close(); } + } } diff --git a/shadowsocks-csharp/Controller/Logging.cs b/shadowsocks-csharp/Controller/Logging.cs index d77a3fbe..803b0c08 100755 --- a/shadowsocks-csharp/Controller/Logging.cs +++ b/shadowsocks-csharp/Controller/Logging.cs @@ -1,23 +1,23 @@ -using Shadowsocks.Util; -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Net.Sockets; -using System.Text; +using System.Net; + +using Shadowsocks.Util; namespace Shadowsocks.Controller { public class Logging { - public static string LogFile; + public static string LogFilePath; public static bool OpenLogFile() { try { - string temppath = Utils.GetTempPath(); - LogFile = Path.Combine(temppath, "shadowsocks.log"); - FileStream fs = new FileStream(LogFile, FileMode.Append); + LogFilePath = Utils.GetTempPath("shadowsocks.log"); + + FileStream fs = new FileStream(LogFilePath, FileMode.Append); StreamWriterWithTimestamp sw = new StreamWriterWithTimestamp(fs); sw.AutoFlush = true; Console.SetOut(sw); @@ -32,11 +32,46 @@ namespace Shadowsocks.Controller } } + private static void WriteToLogFile(object o) + { + Console.WriteLine(o); + } + + public static void Error(object o) + { + WriteToLogFile("[E] " + o); + } + + public static void Info(object o) + { + WriteToLogFile(o); + } + public static void Debug(object o) { +#if DEBUG + WriteToLogFile("[D] " + o); +#endif + } + public static void Debug(EndPoint local, EndPoint remote, int len, string header = null, string tailer = null) + { #if DEBUG - Console.WriteLine(o); + if (header == null && tailer == null) + Debug($"{local} => {remote} (size={len})"); + else if (header == null && tailer != null) + Debug($"{local} => {remote} (size={len}), {tailer}"); + else if (header != null && tailer == null) + Debug($"{header}: {local} => {remote} (size={len})"); + else + Debug($"{header}: {local} => {remote} (size={len}), {tailer}"); +#endif + } + + public static void Debug(Socket sock, int len, string header = null, string tailer = null) + { +#if DEBUG + Debug(sock.LocalEndPoint, sock.RemoteEndPoint, len, header, tailer); #endif } @@ -57,11 +92,19 @@ namespace Shadowsocks.Controller } else if (se.SocketErrorCode == SocketError.NotConnected) { - // close when not connected + // The application tried to send or receive data, and the System.Net.Sockets.Socket is not connected. + } + else if (se.SocketErrorCode == SocketError.HostUnreachable) + { + // There is no network route to the specified host. + } + else if (se.SocketErrorCode == SocketError.TimedOut) + { + // The connection attempt timed out, or the connected host has failed to respond. } else { - Console.WriteLine(e); + Info(e); } } else if (e is ObjectDisposedException) @@ -69,7 +112,7 @@ namespace Shadowsocks.Controller } else { - Console.WriteLine(e); + Info(e); } } } diff --git a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs index 9ed89a49..f9c3c3d7 100644 --- a/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs +++ b/shadowsocks-csharp/Controller/Service/AvailabilityStatistics.cs @@ -9,6 +9,10 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + using Shadowsocks.Model; using Shadowsocks.Util; @@ -30,8 +34,8 @@ namespace Shadowsocks.Controller public Statistics FilteredStatistics { get; private set; } public static readonly DateTime UnknownDateTime = new DateTime(1970, 1, 1); private int Repeat => _config.RepeatTimesNum; - private const int RetryInterval = 2*60*1000; //retry 2 minutes after failed - private int Interval => (int) TimeSpan.FromMinutes(_config.DataCollectionMinutes).TotalMilliseconds; + private const int RetryInterval = 2 * 60 * 1000; //retry 2 minutes after failed + private int Interval => (int)TimeSpan.FromMinutes(_config.DataCollectionMinutes).TotalMilliseconds; private Timer _timer; private State _state; private List _servers; @@ -42,8 +46,7 @@ namespace Shadowsocks.Controller //static constructor to initialize every public static fields before refereced static AvailabilityStatistics() { - var temppath = Utils.GetTempPath(); - AvailabilityStatisticsFile = Path.Combine(temppath, StatisticsFilesName); + AvailabilityStatisticsFile = Utils.GetTempPath(StatisticsFilesName); } public AvailabilityStatistics(Configuration config, StatisticsStrategyConfiguration statisticsConfig) @@ -107,11 +110,18 @@ namespace Shadowsocks.Controller Logging.LogUsefulException(e); return null; } - dynamic obj; - if (!SimpleJson.SimpleJson.TryDeserializeObject(jsonString, out obj)) return null; - string country = obj["country"]; - string city = obj["city"]; - string isp = obj["isp"]; + JObject obj; + try + { + obj = JObject.Parse(jsonString); + } + catch (JsonReaderException) + { + return null; + } + string country = (string)obj["country"]; + string city = (string)obj["city"]; + string isp = (string)obj["isp"]; if (country == null || city == null || isp == null) return null; return new DataList { new DataUnit(State.Geolocation, $"\"{country} {city}\""), @@ -123,11 +133,10 @@ namespace Shadowsocks.Controller { Logging.Debug("Ping " + server.FriendlyName()); if (server.server == "") return null; - var IP = Dns.GetHostAddresses(server.server).First(ip => ip.AddressFamily == AddressFamily.InterNetwork); + var IP = Dns.GetHostAddresses(server.server).First(ip => (ip.AddressFamily == AddressFamily.InterNetwork || ip.AddressFamily == AddressFamily.InterNetworkV6)); var ping = new Ping(); var ret = new List(); - foreach ( - var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString(DateTimePattern))) + foreach (var timestamp in Enumerable.Range(0, Repeat).Select(_ => DateTime.Now.ToString(DateTimePattern))) { //ICMP echo. we can also set options and special bytes try @@ -146,7 +155,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Console.WriteLine($"An exception occured when eveluating {server.FriendlyName()}"); + Logging.Error($"An exception occured while eveluating {server.FriendlyName()}"); Logging.LogUsefulException(e); } } @@ -182,11 +191,11 @@ namespace Shadowsocks.Controller if (!File.Exists(AvailabilityStatisticsFile)) { var headerLine = string.Join(Delimiter, data.Select(kv => kv.Key).ToArray()); - lines = new[] {headerLine, dataLine}; + lines = new[] { headerLine, dataLine }; } else { - lines = new[] {dataLine}; + lines = new[] { dataLine }; } try { @@ -249,30 +258,29 @@ namespace Shadowsocks.Controller Logging.Debug($"loading statistics from {path}"); if (!File.Exists(path)) { - Console.WriteLine($"statistics file does not exist, try to reload {RetryInterval/60/1000} minutes later"); + Console.WriteLine($"statistics file does not exist, try to reload {RetryInterval / 60 / 1000} minutes later"); _timer.Change(RetryInterval, Interval); return; } - RawStatistics = (from l in File.ReadAllLines(path) - .Skip(1) - let strings = l.Split(new[] {","}, StringSplitOptions.RemoveEmptyEntries) - let rawData = new RawStatisticsData - { - Timestamp = ParseExactOrUnknown(strings[0]), - ServerName = strings[1], - ICMPStatus = strings[2], - RoundtripTime = int.Parse(strings[3]), - Geolocation = 5 > strings.Length ? - null - : strings[4], - ISP = 6 > strings.Length ? null : strings[5] - } - group rawData by rawData.ServerName into server - select new - { - ServerName = server.Key, - data = server.ToList() - }).ToDictionary(server => server.ServerName, server=> server.data); + RawStatistics = (from l in File.ReadAllLines(path).Skip(1) + let strings = l.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries) + let rawData = new RawStatisticsData + { + Timestamp = ParseExactOrUnknown(strings[0]), + ServerName = strings[1], + ICMPStatus = strings[2], + RoundtripTime = int.Parse(strings[3]), + Geolocation = 5 > strings.Length ? + null + : strings[4], + ISP = 6 > strings.Length ? null : strings[5] + } + group rawData by rawData.ServerName into server + select new + { + ServerName = server.Key, + data = server.ToList() + }).ToDictionary(server => server.ServerName, server => server.data); } catch (Exception e) { @@ -301,7 +309,7 @@ namespace Shadowsocks.Controller public string ICMPStatus; public int RoundtripTime; public string Geolocation; - public string ISP ; + public string ISP; } public class StatisticsData @@ -311,6 +319,5 @@ namespace Shadowsocks.Controller public int MinResponse; public int MaxResponse; } - } } diff --git a/shadowsocks-csharp/Controller/Service/GfwListUpdater.cs b/shadowsocks-csharp/Controller/Service/GfwListUpdater.cs index 493defa4..2497090d 100644 --- a/shadowsocks-csharp/Controller/Service/GfwListUpdater.cs +++ b/shadowsocks-csharp/Controller/Service/GfwListUpdater.cs @@ -1,12 +1,14 @@ using System; using System.Collections.Generic; -using System.Text; -using System.Net; using System.IO; +using System.Net; +using System.Text; + +using Newtonsoft.Json; + +using Shadowsocks.Model; using Shadowsocks.Properties; -using SimpleJson; using Shadowsocks.Util; -using Shadowsocks.Model; namespace Shadowsocks.Controller { @@ -14,10 +16,6 @@ namespace Shadowsocks.Controller { private const string GFWLIST_URL = "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"; - private static string PAC_FILE = PACServer.PAC_FILE; - - private static string USER_RULE_FILE = PACServer.USER_RULE_FILE; - public event EventHandler UpdateCompleted; public event ErrorEventHandler Error; @@ -36,30 +34,39 @@ namespace Shadowsocks.Controller { try { + File.WriteAllText(Utils.GetTempPath("gfwlist.txt"), e.Result, Encoding.UTF8); List lines = ParseResult(e.Result); - if (File.Exists(USER_RULE_FILE)) + if (File.Exists(PACServer.USER_RULE_FILE)) { - string local = File.ReadAllText(USER_RULE_FILE, Encoding.UTF8); + string local = File.ReadAllText(PACServer.USER_RULE_FILE, Encoding.UTF8); string[] rules = local.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - foreach(string rule in rules) + foreach (string rule in rules) { if (rule.StartsWith("!") || rule.StartsWith("[")) continue; lines.Add(rule); } } - string abpContent = Utils.UnGzip(Resources.abp_js); - abpContent = abpContent.Replace("__RULES__", SimpleJson.SimpleJson.SerializeObject(lines)); - if (File.Exists(PAC_FILE)) + string abpContent; + if (File.Exists(PACServer.USER_ABP_FILE)) + { + abpContent = File.ReadAllText(PACServer.USER_ABP_FILE, Encoding.UTF8); + } + else + { + abpContent = Utils.UnGzip(Resources.abp_js); + } + abpContent = abpContent.Replace("__RULES__", JsonConvert.SerializeObject(lines, Formatting.Indented)); + if (File.Exists(PACServer.PAC_FILE)) { - string original = File.ReadAllText(PAC_FILE, Encoding.UTF8); + string original = File.ReadAllText(PACServer.PAC_FILE, Encoding.UTF8); if (original == abpContent) { UpdateCompleted(this, new ResultEventArgs(false)); return; } } - File.WriteAllText(PAC_FILE, abpContent, Encoding.UTF8); + File.WriteAllText(PACServer.PAC_FILE, abpContent, Encoding.UTF8); if (UpdateCompleted != null) { UpdateCompleted(this, new ResultEventArgs(true)); @@ -82,7 +89,7 @@ namespace Shadowsocks.Controller http.DownloadStringAsync(new Uri(GFWLIST_URL)); } - public List ParseResult(string response) + public static List ParseResult(string response) { byte[] bytes = Convert.FromBase64String(response); string content = Encoding.ASCII.GetString(bytes); diff --git a/shadowsocks-csharp/Controller/Service/Listener.cs b/shadowsocks-csharp/Controller/Service/Listener.cs index fe59fe98..e9744988 100644 --- a/shadowsocks-csharp/Controller/Service/Listener.cs +++ b/shadowsocks-csharp/Controller/Service/Listener.cs @@ -1,10 +1,10 @@ -using Shadowsocks.Model; -using System; +using System; using System.Collections.Generic; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; -using System.Text; + +using Shadowsocks.Model; namespace Shadowsocks.Controller { @@ -78,10 +78,8 @@ namespace Shadowsocks.Controller _tcpSocket.Listen(1024); // Start an asynchronous socket to listen for connections. - Console.WriteLine("Shadowsocks started"); - _tcpSocket.BeginAccept( - new AsyncCallback(AcceptCallback), - _tcpSocket); + Logging.Info("Shadowsocks started"); + _tcpSocket.BeginAccept(new AsyncCallback(AcceptCallback), _tcpSocket); UDPState udpState = new UDPState(); _udpSocket.BeginReceiveFrom(udpState.buffer, 0, udpState.buffer.Length, 0, ref udpState.remoteEndPoint, new AsyncCallback(RecvFromCallback), udpState); } @@ -163,7 +161,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Console.WriteLine(e); + Logging.LogUsefulException(e); } finally { @@ -208,7 +206,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Console.WriteLine(e); + Logging.LogUsefulException(e); conn.Close(); } } diff --git a/shadowsocks-csharp/Controller/Service/PACServer.cs b/shadowsocks-csharp/Controller/Service/PACServer.cs index f8fc80e8..c6853331 100644 --- a/shadowsocks-csharp/Controller/Service/PACServer.cs +++ b/shadowsocks-csharp/Controller/Service/PACServer.cs @@ -1,31 +1,33 @@ -using Shadowsocks.Model; -using Shadowsocks.Properties; -using Shadowsocks.Util; -using System; -using System.Collections.Generic; -using System.Diagnostics; +using System; +using System.Collections; using System.IO; -using System.IO.Compression; using System.Net; using System.Net.Sockets; using System.Text; +using Shadowsocks.Model; +using Shadowsocks.Properties; +using Shadowsocks.Util; + namespace Shadowsocks.Controller { class PACServer : Listener.Service { - public static string PAC_FILE = "pac.txt"; - - public static string USER_RULE_FILE = "user-rule.txt"; + public static readonly string PAC_FILE = "pac.txt"; + public static readonly string USER_RULE_FILE = "user-rule.txt"; + public static readonly string USER_ABP_FILE = "abp.txt"; - FileSystemWatcher watcher; + FileSystemWatcher PACFileWatcher; + FileSystemWatcher UserRuleFileWatcher; private Configuration _config; public event EventHandler PACFileChanged; + public event EventHandler UserRuleFileChanged; public PACServer() { this.WatchPacFile(); + this.WatchUserRuleFile(); } public void UpdateConfiguration(Configuration config) @@ -46,7 +48,7 @@ namespace Shadowsocks.Controller bool hostMatch = false, pathMatch = false, useSocks = false; foreach (string line in lines) { - string[] kv = line.Split(new char[]{':'}, 2); + string[] kv = line.Split(new char[] { ':' }, 2); if (kv.Length == 2) { if (kv[0] == "Host") @@ -56,14 +58,14 @@ namespace Shadowsocks.Controller hostMatch = true; } } - else if (kv[0] == "User-Agent") - { - // we need to drop connections when changing servers - /* if (kv[1].IndexOf("Chrome") >= 0) - { - useSocks = true; - } */ - } + //else if (kv[0] == "User-Agent") + //{ + // // we need to drop connections when changing servers + // if (kv[1].IndexOf("Chrome") >= 0) + // { + // useSocks = true; + // } + //} } else if (kv.Length == 1) { @@ -142,14 +144,14 @@ Content-Type: application/x-ns-proxy-autoconfig Content-Length: {0} Connection: Close -", System.Text.Encoding.UTF8.GetBytes(pac).Length) + pac; - byte[] response = System.Text.Encoding.UTF8.GetBytes(text); +", Encoding.UTF8.GetBytes(pac).Length) + pac; + byte[] response = Encoding.UTF8.GetBytes(text); socket.BeginSend(response, 0, response.Length, 0, new AsyncCallback(SendCallback), socket); - Util.Utils.ReleaseMemory(true); + Utils.ReleaseMemory(true); } catch (Exception e) { - Console.WriteLine(e); + Logging.LogUsefulException(e); socket.Close(); } } @@ -167,27 +169,78 @@ Connection: Close private void WatchPacFile() { - if (watcher != null) - { - watcher.Dispose(); - } - watcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); - watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; - watcher.Filter = PAC_FILE; - watcher.Changed += Watcher_Changed; - watcher.Created += Watcher_Changed; - watcher.Deleted += Watcher_Changed; - watcher.Renamed += Watcher_Changed; - watcher.EnableRaisingEvents = true; + if (PACFileWatcher != null) + { + PACFileWatcher.Dispose(); + } + PACFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); + PACFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; + PACFileWatcher.Filter = PAC_FILE; + PACFileWatcher.Changed += PACFileWatcher_Changed; + PACFileWatcher.Created += PACFileWatcher_Changed; + PACFileWatcher.Deleted += PACFileWatcher_Changed; + PACFileWatcher.Renamed += PACFileWatcher_Changed; + PACFileWatcher.EnableRaisingEvents = true; } - private void Watcher_Changed(object sender, FileSystemEventArgs e) + private void WatchUserRuleFile() { - if (PACFileChanged != null) + if (UserRuleFileWatcher != null) { - PACFileChanged(this, new EventArgs()); + UserRuleFileWatcher.Dispose(); + } + UserRuleFileWatcher = new FileSystemWatcher(Directory.GetCurrentDirectory()); + UserRuleFileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; + UserRuleFileWatcher.Filter = USER_RULE_FILE; + UserRuleFileWatcher.Changed += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Created += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Deleted += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.Renamed += UserRuleFileWatcher_Changed; + UserRuleFileWatcher.EnableRaisingEvents = true; + } + + #region FileSystemWatcher.OnChanged() + // FileSystemWatcher Changed event is raised twice + // http://stackoverflow.com/questions/1764809/filesystemwatcher-changed-event-is-raised-twice + private static Hashtable fileChangedTime = new Hashtable(); + + private void PACFileWatcher_Changed(object sender, FileSystemEventArgs e) + { + string path = e.FullPath.ToString(); + string currentLastWriteTime = File.GetLastWriteTime(e.FullPath).ToString(); + + // if there is no path info stored yet or stored path has different time of write then the one now is inspected + if (!fileChangedTime.ContainsKey(path) || fileChangedTime[path].ToString() != currentLastWriteTime) + { + if (PACFileChanged != null) + { + Logging.Info($"Detected: PAC file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); + PACFileChanged(this, new EventArgs()); + } + + // lastly we update the last write time in the hashtable + fileChangedTime[path] = currentLastWriteTime; + } + } + + private void UserRuleFileWatcher_Changed(object sender, FileSystemEventArgs e) + { + string path = e.FullPath.ToString(); + string currentLastWriteTime = File.GetLastWriteTime(e.FullPath).ToString(); + + // if there is no path info stored yet or stored path has different time of write then the one now is inspected + if (!fileChangedTime.ContainsKey(path) || fileChangedTime[path].ToString() != currentLastWriteTime) + { + if (UserRuleFileChanged != null) + { + Logging.Info($"Detected: User Rule file '{e.Name}' was {e.ChangeType.ToString().ToLower()}."); + UserRuleFileChanged(this, new EventArgs()); + } + // lastly we update the last write time in the hashtable + fileChangedTime[path] = currentLastWriteTime; } } + #endregion private string GetPACAddress(byte[] requestBuf, int length, IPEndPoint localEndPoint, bool useSocks) { @@ -202,7 +255,7 @@ Connection: Close //} //catch (Exception e) //{ - // Console.WriteLine(e); + // Logging.LogUsefulException(e); //} return (useSocks ? "SOCKS5 " : "PROXY ") + localEndPoint.Address + ":" + this._config.localPort + ";"; } diff --git a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs index 5b784bcd..f824c2e5 100644 --- a/shadowsocks-csharp/Controller/Service/PolipoRunner.cs +++ b/shadowsocks-csharp/Controller/Service/PolipoRunner.cs @@ -1,14 +1,14 @@ -using Shadowsocks.Model; -using Shadowsocks.Properties; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.IO.Compression; -using System.Text; -using System.Net.NetworkInformation; using System.Net; +using System.Net.NetworkInformation; using System.Runtime.InteropServices; +using System.Text; + +using Shadowsocks.Model; +using Shadowsocks.Properties; using Shadowsocks.Util; namespace Shadowsocks.Controller @@ -16,16 +16,14 @@ namespace Shadowsocks.Controller class PolipoRunner { private Process _process; - private static string temppath; private int _runningPort; static PolipoRunner() { - temppath = Utils.GetTempPath(); try { - FileManager.UncompressFile(temppath + "/ss_privoxy.exe", Resources.privoxy_exe); - FileManager.UncompressFile(temppath + "/mgwz.dll", Resources.mgwz_dll); + FileManager.UncompressFile(Utils.GetTempPath("ss_privoxy.exe"), Resources.privoxy_exe); + FileManager.UncompressFile(Utils.GetTempPath("mgwz.dll"), Resources.mgwz_dll); } catch (IOException e) { @@ -56,7 +54,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Console.WriteLine(e.ToString()); + Logging.LogUsefulException(e); } } string polipoConfig = Resources.privoxy_conf; @@ -64,20 +62,16 @@ namespace Shadowsocks.Controller polipoConfig = polipoConfig.Replace("__SOCKS_PORT__", configuration.localPort.ToString()); polipoConfig = polipoConfig.Replace("__POLIPO_BIND_PORT__", _runningPort.ToString()); polipoConfig = polipoConfig.Replace("__POLIPO_BIND_IP__", configuration.shareOverLan ? "0.0.0.0" : "127.0.0.1"); - FileManager.ByteArrayToFile(temppath + "/privoxy.conf", System.Text.Encoding.UTF8.GetBytes(polipoConfig)); + FileManager.ByteArrayToFile(Utils.GetTempPath("privoxy.conf"), Encoding.UTF8.GetBytes(polipoConfig)); - if (!(temppath.EndsWith("\\") || temppath.EndsWith("/"))) { - temppath = temppath + "\\"; - } _process = new Process(); // Configure the process using the StartInfo properties. - _process.StartInfo.FileName = temppath + "ss_privoxy.exe"; - _process.StartInfo.Arguments = " \"" + temppath + "privoxy.conf\""; + _process.StartInfo.FileName = "ss_privoxy.exe"; + _process.StartInfo.Arguments = "privoxy.conf"; + _process.StartInfo.WorkingDirectory = Utils.GetTempPath(); _process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden; _process.StartInfo.UseShellExecute = true; _process.StartInfo.CreateNoWindow = true; - //_process.StartInfo.RedirectStandardOutput = true; - //_process.StartInfo.RedirectStandardError = true; _process.Start(); } RefreshTrayArea(); @@ -94,7 +88,7 @@ namespace Shadowsocks.Controller } catch (Exception e) { - Console.WriteLine(e.ToString()); + Logging.LogUsefulException(e); } _process = null; } diff --git a/shadowsocks-csharp/Controller/Service/PortForwarder.cs b/shadowsocks-csharp/Controller/Service/PortForwarder.cs index 63848079..4f78a24c 100644 --- a/shadowsocks-csharp/Controller/Service/PortForwarder.cs +++ b/shadowsocks-csharp/Controller/Service/PortForwarder.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Net; using System.Net.Sockets; -using System.Text; namespace Shadowsocks.Controller { @@ -140,7 +138,6 @@ namespace Shadowsocks.Controller } else { - //Console.WriteLine("bytesRead: " + bytesRead.ToString()); _local.Shutdown(SocketShutdown.Send); _localShutdown = true; CheckClose(); diff --git a/shadowsocks-csharp/Controller/Service/TCPRelay.cs b/shadowsocks-csharp/Controller/Service/TCPRelay.cs index eef5948c..2fd3c149 100644 --- a/shadowsocks-csharp/Controller/Service/TCPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/TCPRelay.cs @@ -1,31 +1,30 @@ using System; using System.Collections.Generic; -using System.Text; -using System.Net.Sockets; using System.Net; +using System.Net.Sockets; +using System.Timers; + +using Shadowsocks.Controller.Strategy; using Shadowsocks.Encryption; using Shadowsocks.Model; -using Shadowsocks.Controller.Strategy; -using System.Timers; namespace Shadowsocks.Controller { - class TCPRelay : Listener.Service { private ShadowsocksController _controller; private DateTime _lastSweepTime; - public ISet Handlers + public ISet Handlers { get; set; } public TCPRelay(ShadowsocksController controller) { - this._controller = controller; - this.Handlers = new HashSet(); - this._lastSweepTime = DateTime.Now; + _controller = controller; + Handlers = new HashSet(); + _lastSweepTime = DateTime.Now; } public bool Handle(byte[] firstPacket, int length, Socket socket, object state) @@ -39,22 +38,21 @@ namespace Shadowsocks.Controller return false; } socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true); - Handler handler = new Handler(); + TCPHandler handler = new TCPHandler(this); handler.connection = socket; handler.controller = _controller; handler.relay = this; handler.Start(firstPacket, length); - IList handlersToClose = new List(); - lock (this.Handlers) + IList handlersToClose = new List(); + lock (Handlers) { - this.Handlers.Add(handler); - Logging.Debug($"connections: {Handlers.Count}"); + Handlers.Add(handler); DateTime now = DateTime.Now; if (now - _lastSweepTime > TimeSpan.FromSeconds(1)) { _lastSweepTime = now; - foreach (Handler handler1 in this.Handlers) + foreach (TCPHandler handler1 in Handlers) { if (now - handler1.lastActivity > TimeSpan.FromSeconds(900)) { @@ -63,18 +61,28 @@ namespace Shadowsocks.Controller } } } - foreach (Handler handler1 in handlersToClose) + foreach (TCPHandler handler1 in handlersToClose) { - Logging.Debug("Closing timed out connection"); + Logging.Debug("Closing timed out TCP connection."); handler1.Close(); } - return true; + return true; + } + + public void UpdateInboundCounter(long n) + { + _controller.UpdateInboundCounter(n); + } + + public void UpdateOutboundCounter(long n) + { + _controller.UpdateOutboundCounter(n); } } - class Handler + class TCPHandler { - //public Encryptor encryptor; + // public Encryptor encryptor; public IEncryptor encryptor; public Server server; // Client socket. @@ -85,6 +93,7 @@ namespace Shadowsocks.Controller public DateTime lastActivity; + private const int maxRetry = 4; private int retryCount = 0; private bool connected; @@ -117,6 +126,12 @@ namespace Shadowsocks.Controller private object decryptionLock = new object(); private DateTime _startConnectTime; + private TCPRelay tcprelay; // TODO: tcprelay ?= relay + + public TCPHandler(TCPRelay tcprelay) + { + this.tcprelay = tcprelay; + } public void CreateRemote() { @@ -125,23 +140,23 @@ namespace Shadowsocks.Controller { throw new ArgumentException("No server configured"); } - this.encryptor = EncryptorFactory.GetEncryptor(server.method, server.password, server.one_time_auth, false); + encryptor = EncryptorFactory.GetEncryptor(server.method, server.password, server.auth, false); this.server = server; } public void Start(byte[] firstPacket, int length) { - this._firstPacket = firstPacket; - this._firstPacketLength = length; - this.HandshakeReceive(); - this.lastActivity = DateTime.Now; + _firstPacket = firstPacket; + _firstPacketLength = length; + HandshakeReceive(); + lastActivity = DateTime.Now; } private void CheckClose() { if (connectionShutdown && remoteShutdown) { - this.Close(); + Close(); } } @@ -149,7 +164,6 @@ namespace Shadowsocks.Controller { lock (relay.Handlers) { - Logging.Debug($"connections: {relay.Handlers.Count}"); relay.Handlers.Remove(this); } lock (this) @@ -213,19 +227,19 @@ namespace Shadowsocks.Controller { // reject socks 4 response = new byte[] { 0, 91 }; - Console.WriteLine("socks 5 protocol error"); + Logging.Error("socks 5 protocol error"); } connection.BeginSend(response, 0, response.Length, 0, new AsyncCallback(HandshakeSendCallback), null); } else { - this.Close(); + Close(); } } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -239,20 +253,19 @@ namespace Shadowsocks.Controller { connection.EndSend(ar); - // +----+-----+-------+------+----------+----------+ - // |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | - // +----+-----+-------+------+----------+----------+ - // | 1 | 1 | X'00' | 1 | Variable | 2 | - // +----+-----+-------+------+----------+----------+ + // +-----+-----+-------+------+----------+----------+ + // | VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + // +-----+-----+-------+------+----------+----------+ + // | 1 | 1 | X'00' | 1 | Variable | 2 | + // +-----+-----+-------+------+----------+----------+ // Skip first 3 bytes // TODO validate - connection.BeginReceive(connetionRecvBuffer, 0, 3, 0, - new AsyncCallback(handshakeReceive2Callback), null); + connection.BeginReceive(connetionRecvBuffer, 0, 3, 0, new AsyncCallback(handshakeReceive2Callback), null); } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -281,14 +294,14 @@ namespace Shadowsocks.Controller } else { - Console.WriteLine("failed to recv data in handshakeReceive2Callback"); - this.Close(); + Logging.Debug("failed to recv data in Shadowsocks.Controller.TCPHandler.handshakeReceive2Callback()"); + Close(); } } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -324,27 +337,27 @@ namespace Shadowsocks.Controller if (ar.AsyncState != null) { connection.EndSend(ar); - connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, - new AsyncCallback(ReadAll), null); + Logging.Debug(remote, RecvSize, "TCP Relay"); + connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(ReadAll), null); } else { int bytesRead = connection.EndReceive(ar); if (bytesRead > 0) { - connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, - new AsyncCallback(ReadAll), null); + Logging.Debug(remote, RecvSize, "TCP Relay"); + connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(ReadAll), null); } else { - this.Close(); + Close(); } } } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -360,15 +373,16 @@ namespace Shadowsocks.Controller catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } + // inner class private class ServerTimer : Timer { public Server Server; - public ServerTimer(int p) :base(p) + public ServerTimer(int p) : base(p) { } } @@ -402,13 +416,12 @@ namespace Shadowsocks.Controller connected = false; // Connect to the remote endpoint. - remote.BeginConnect(remoteEP, - new AsyncCallback(ConnectCallback), connectTimer); + remote.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), connectTimer); } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -424,22 +437,22 @@ namespace Shadowsocks.Controller { strategy.SetFailure(server); } - Console.WriteLine(String.Format("{0} timed out", server.FriendlyName())); + Logging.Info($"{server.FriendlyName()} timed out"); remote.Close(); RetryConnect(); } private void RetryConnect() { - if (retryCount < 4) + if (retryCount < maxRetry) { - Logging.Debug("Connection failed, retrying"); + Logging.Debug($"Connection failed, retry ({retryCount})"); StartConnect(); retryCount++; } else { - this.Close(); + Close(); } } @@ -463,8 +476,7 @@ namespace Shadowsocks.Controller connected = true; - //Console.WriteLine("Socket connected to {0}", - // remote.RemoteEndPoint.ToString()); + Logging.Debug($"Socket connected to {remote.RemoteEndPoint}"); var latency = DateTime.Now - _startConnectTime; IStrategy strategy = controller.GetCurrentStrategy(); @@ -501,15 +513,13 @@ namespace Shadowsocks.Controller } try { - remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, - new AsyncCallback(PipeRemoteReceiveCallback), null); - connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, - new AsyncCallback(PipeConnectionReceiveCallback), null); + remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeRemoteReceiveCallback), null); + connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeConnectionReceiveCallback), null); } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -523,10 +533,11 @@ namespace Shadowsocks.Controller { int bytesRead = remote.EndReceive(ar); totalRead += bytesRead; + tcprelay.UpdateInboundCounter(bytesRead); if (bytesRead > 0) { - this.lastActivity = DateTime.Now; + lastActivity = DateTime.Now; int bytesToSend; lock (decryptionLock) { @@ -536,33 +547,33 @@ namespace Shadowsocks.Controller } encryptor.Decrypt(remoteRecvBuffer, bytesRead, remoteSendBuffer, out bytesToSend); } + Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeRemoteReceiveCallback() (download)"); connection.BeginSend(remoteSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeConnectionSendCallback), null); IStrategy strategy = controller.GetCurrentStrategy(); if (strategy != null) { - strategy.UpdateLastRead(this.server); + strategy.UpdateLastRead(server); } } else { - //Console.WriteLine("bytesRead: " + bytesRead.ToString()); connection.Shutdown(SocketShutdown.Send); connectionShutdown = true; CheckClose(); - if (totalRead == 0) - { - // closed before anything received, reports as failure - // disable this feature - // controller.GetCurrentStrategy().SetFailure(this.server); - } + //if (totalRead == 0) + //{ + // // closed before anything received, reports as failure + // // disable this feature + // controller.GetCurrentStrategy().SetFailure(this.server); + //} } } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -588,12 +599,14 @@ namespace Shadowsocks.Controller } encryptor.Encrypt(connetionRecvBuffer, bytesRead, connetionSendBuffer, out bytesToSend); } + Logging.Debug(remote, bytesToSend, "TCP Relay", "@PipeConnectionReceiveCallback() (upload)"); + tcprelay.UpdateOutboundCounter(bytesToSend); remote.BeginSend(connetionSendBuffer, 0, bytesToSend, 0, new AsyncCallback(PipeRemoteSendCallback), null); IStrategy strategy = controller.GetCurrentStrategy(); if (strategy != null) { - strategy.UpdateLastWrite(this.server); + strategy.UpdateLastWrite(server); } } else @@ -606,7 +619,7 @@ namespace Shadowsocks.Controller catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -619,13 +632,12 @@ namespace Shadowsocks.Controller try { remote.EndSend(ar); - connection.BeginReceive(this.connetionRecvBuffer, 0, RecvSize, 0, - new AsyncCallback(PipeConnectionReceiveCallback), null); + connection.BeginReceive(connetionRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeConnectionReceiveCallback), null); } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } @@ -638,13 +650,12 @@ namespace Shadowsocks.Controller try { connection.EndSend(ar); - remote.BeginReceive(this.remoteRecvBuffer, 0, RecvSize, 0, - new AsyncCallback(PipeRemoteReceiveCallback), null); + remote.BeginReceive(remoteRecvBuffer, 0, RecvSize, 0, new AsyncCallback(PipeRemoteReceiveCallback), null); } catch (Exception e) { Logging.LogUsefulException(e); - this.Close(); + Close(); } } } diff --git a/shadowsocks-csharp/Controller/Service/UDPRelay.cs b/shadowsocks-csharp/Controller/Service/UDPRelay.cs index b449c412..e1325a3d 100644 --- a/shadowsocks-csharp/Controller/Service/UDPRelay.cs +++ b/shadowsocks-csharp/Controller/Service/UDPRelay.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; -using System.Text; -using Shadowsocks.Encryption; -using Shadowsocks.Model; -using System.Net.Sockets; using System.Net; +using System.Net.Sockets; using System.Runtime.CompilerServices; + using Shadowsocks.Controller.Strategy; +using Shadowsocks.Encryption; +using Shadowsocks.Model; namespace Shadowsocks.Controller { @@ -14,6 +14,10 @@ namespace Shadowsocks.Controller { private ShadowsocksController _controller; private LRUCache _cache; + + public long outbound = 0; + public long inbound = 0; + public UDPRelay(ShadowsocksController controller) { this._controller = controller; @@ -70,23 +74,27 @@ namespace Shadowsocks.Controller } _remoteEndPoint = new IPEndPoint(ipAddress, server.server_port); _remote = new Socket(_remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); - } + public void Send(byte[] data, int length) { - IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password, _server.one_time_auth, true); + IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password, _server.auth, true); byte[] dataIn = new byte[length - 3 + IVEncryptor.ONETIMEAUTH_BYTES]; Array.Copy(data, 3, dataIn, 0, length - 3); byte[] dataOut = new byte[length - 3 + 16 + IVEncryptor.ONETIMEAUTH_BYTES]; int outlen; encryptor.Encrypt(dataIn, length - 3, dataOut, out outlen); + Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); _remote.SendTo(dataOut, outlen, SocketFlags.None, _remoteEndPoint); } + public void Receive() { EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0); + Logging.Debug($"++++++Receive Server Port, size:" + _buffer.Length); _remote.BeginReceiveFrom(_buffer, 0, _buffer.Length, 0, ref remoteEndPoint, new AsyncCallback(RecvFromCallback), null); } + public void RecvFromCallback(IAsyncResult ar) { try @@ -97,12 +105,13 @@ namespace Shadowsocks.Controller byte[] dataOut = new byte[bytesRead]; int outlen; - IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password, _server.one_time_auth, true); + IEncryptor encryptor = EncryptorFactory.GetEncryptor(_server.method, _server.password, _server.auth, true); encryptor.Decrypt(_buffer, bytesRead, dataOut, out outlen); byte[] sendBuf = new byte[outlen + 3]; Array.Copy(dataOut, 0, sendBuf, 3, outlen); + Logging.Debug(_localEndPoint, _remoteEndPoint, outlen, "UDP Relay"); _local.SendTo(sendBuf, outlen + 3, 0, _localEndPoint); Receive(); } @@ -118,6 +127,7 @@ namespace Shadowsocks.Controller { } } + public void Close() { try @@ -139,7 +149,6 @@ namespace Shadowsocks.Controller } } - // cc by-sa 3.0 http://stackoverflow.com/a/3719378/1124054 class LRUCache where V : UDPRelay.UDPHandler { diff --git a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs index 643c7014..4701f2d5 100644 --- a/shadowsocks-csharp/Controller/Service/UpdateChecker.cs +++ b/shadowsocks-csharp/Controller/Service/UpdateChecker.cs @@ -1,12 +1,9 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Net; -using System.Reflection; -using System.Text; using System.Text.RegularExpressions; -using System.IO; -using SimpleJson; + +using Newtonsoft.Json.Linq; using Shadowsocks.Model; using Shadowsocks.Util; @@ -26,7 +23,35 @@ namespace Shadowsocks.Controller public string LatestVersionLocalName; public event EventHandler CheckUpdateCompleted; - public const string Version = "2.5.8"; + public const string Version = "2.5.8.2"; + + private class CheckUpdateTimer : System.Timers.Timer + { + public Configuration config; + + public CheckUpdateTimer(int p) : base(p) + { + } + } + + public void CheckUpdate(Configuration config, int delay) + { + CheckUpdateTimer timer = new CheckUpdateTimer(delay); + timer.AutoReset = false; + timer.Elapsed += Timer_Elapsed; + timer.config = config; + timer.Enabled = true; + } + + private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) + { + CheckUpdateTimer timer = (CheckUpdateTimer)sender; + Configuration config = timer.config; + timer.Elapsed -= Timer_Elapsed; + timer.Enabled = false; + timer.Dispose(); + CheckUpdate(config); + } public void CheckUpdate(Configuration config) { @@ -34,6 +59,7 @@ namespace Shadowsocks.Controller try { + Logging.Debug("Checking updates..."); WebClient http = CreateWebClient(); http.DownloadStringCompleted += http_DownloadStringCompleted; http.DownloadStringAsync(new Uri(UpdateURL)); @@ -50,26 +76,28 @@ namespace Shadowsocks.Controller { string response = e.Result; - JsonArray result = (JsonArray)SimpleJson.SimpleJson.DeserializeObject(e.Result); + JArray result = JArray.Parse(response); List asserts = new List(); - foreach (JsonObject release in result) + if (result != null) { - if ((bool)release["prerelease"]) - { - continue; - } - foreach (JsonObject asset in (JsonArray)release["assets"]) + foreach (JObject release in result) { - Asset ass = new Asset(); - ass.Parse(asset); - if (ass.IsNewVersion(Version)) + if ((bool)release["prerelease"]) + { + continue; + } + foreach (JObject asset in (JArray)release["assets"]) { - asserts.Add(ass); + Asset ass = new Asset(); + ass.Parse(asset); + if (ass.IsNewVersion(Version)) + { + asserts.Add(ass); + } } } } - if (asserts.Count != 0) { SortByVersions(asserts); @@ -81,9 +109,13 @@ namespace Shadowsocks.Controller startDownload(); } - else if (CheckUpdateCompleted != null) + else { - CheckUpdateCompleted(this, new EventArgs()); + Logging.Debug("No update is available"); + if (CheckUpdateCompleted != null) + { + CheckUpdateCompleted(this, new EventArgs()); + } } } catch (Exception ex) @@ -96,8 +128,7 @@ namespace Shadowsocks.Controller { try { - string temppath = Utils.GetTempPath(); - LatestVersionLocalName = Path.Combine(temppath, LatestVersionName); + LatestVersionLocalName = Utils.GetTempPath(LatestVersionName); WebClient http = CreateWebClient(); http.DownloadFileCompleted += Http_DownloadFileCompleted; http.DownloadFileAsync(new Uri(LatestVersionURL), LatestVersionLocalName); @@ -112,11 +143,12 @@ namespace Shadowsocks.Controller { try { - if(e.Error != null) + if (e.Error != null) { Logging.LogUsefulException(e.Error); return; } + Logging.Debug($"New version {LatestVersionNumber} found: {LatestVersionLocalName}"); if (CheckUpdateCompleted != null) { CheckUpdateCompleted(this, new EventArgs()); @@ -161,7 +193,7 @@ namespace Shadowsocks.Controller return CompareVersion(version, currentVersion) > 0; } - public void Parse(JsonObject asset) + public void Parse(JObject asset) { name = (string)asset["name"]; browser_download_url = (string)asset["browser_download_url"]; @@ -207,6 +239,5 @@ namespace Shadowsocks.Controller return Asset.CompareVersion(x.version, y.version); } } - } } diff --git a/shadowsocks-csharp/Controller/ShadowsocksController.cs b/shadowsocks-csharp/Controller/ShadowsocksController.cs index 7761e3f8..f7d50bd3 100755 --- a/shadowsocks-csharp/Controller/ShadowsocksController.cs +++ b/shadowsocks-csharp/Controller/ShadowsocksController.cs @@ -1,12 +1,17 @@ -using System.IO; -using Shadowsocks.Model; -using System; +using System; using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; using System.Text; using System.Threading; -using System.Net.Sockets; + +using Newtonsoft.Json; + using Shadowsocks.Controller.Strategy; -using System.Net; +using Shadowsocks.Model; +using Shadowsocks.Properties; +using Shadowsocks.Util; namespace Shadowsocks.Controller { @@ -28,6 +33,9 @@ namespace Shadowsocks.Controller public AvailabilityStatistics availabilityStatistics { get; private set; } public StatisticsStrategyConfiguration StatisticsConfiguration { get; private set; } + public long inboundCounter = 0; + public long outboundCounter = 0; + private bool stopped = false; private bool _systemProxyIsDirty = false; @@ -60,7 +68,6 @@ namespace Shadowsocks.Controller StartReleasingMemory(); } - public void Start() { Reload(); @@ -246,7 +253,7 @@ namespace Shadowsocks.Controller public static string GetQRCode(Server server) { string parts = server.method + ":" + server.password + "@" + server.server + ":" + server.server_port; - string base64 = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); + string base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(parts)); return "ss://" + base64; } @@ -300,6 +307,16 @@ namespace Shadowsocks.Controller Configuration.Save(_config); } + public void UpdateInboundCounter(long n) + { + Interlocked.Add(ref inboundCounter, n); + } + + public void UpdateOutboundCounter(long n) + { + Interlocked.Add(ref outboundCounter, n); + } + protected void Reload() { // some logic in configuration updated the config when saving, we need to read it again @@ -314,6 +331,7 @@ namespace Shadowsocks.Controller { _pacServer = new PACServer(); _pacServer.PACFileChanged += pacServer_PACFileChanged; + _pacServer.UserRuleFileChanged += pacServer_UserRuleFileChanged; } _pacServer.UpdateConfiguration(_config); if (gfwListUpdater == null) @@ -380,7 +398,7 @@ namespace Shadowsocks.Controller } UpdateSystemProxy(); - Util.Utils.ReleaseMemory(true); + Utils.ReleaseMemory(true); } protected void SaveConfig(Configuration newConfig) @@ -424,6 +442,47 @@ namespace Shadowsocks.Controller UpdatePACFromGFWListError(this, e); } + private void pacServer_UserRuleFileChanged(object sender, EventArgs e) + { + // TODO: this is a dirty hack. (from code GListUpdater.http_DownloadStringCompleted()) + if (!File.Exists(Utils.GetTempPath("gfwlist.txt"))) + { + UpdatePACFromGFWList(); + return; + } + List lines = GFWListUpdater.ParseResult(File.ReadAllText(Utils.GetTempPath("gfwlist.txt"))); + if (File.Exists(PACServer.USER_RULE_FILE)) + { + string local = File.ReadAllText(PACServer.USER_RULE_FILE, Encoding.UTF8); + string[] rules = local.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + foreach (string rule in rules) + { + if (rule.StartsWith("!") || rule.StartsWith("[")) + continue; + lines.Add(rule); + } + } + string abpContent; + if (File.Exists(PACServer.USER_ABP_FILE)) + { + abpContent = File.ReadAllText(PACServer.USER_ABP_FILE, Encoding.UTF8); + } + else + { + abpContent = Utils.UnGzip(Resources.abp_js); + } + abpContent = abpContent.Replace("__RULES__", JsonConvert.SerializeObject(lines, Formatting.Indented)); + if (File.Exists(PACServer.PAC_FILE)) + { + string original = File.ReadAllText(PACServer.PAC_FILE, Encoding.UTF8); + if (original == abpContent) + { + return; + } + } + File.WriteAllText(PACServer.PAC_FILE, abpContent, Encoding.UTF8); + } + private void StartReleasingMemory() { _ramThread = new Thread(new ThreadStart(ReleaseMemory)); @@ -435,7 +494,7 @@ namespace Shadowsocks.Controller { while (true) { - Util.Utils.ReleaseMemory(false); + Utils.ReleaseMemory(false); Thread.Sleep(30 * 1000); } } diff --git a/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs b/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs index fa05a0c1..ba25ee18 100644 --- a/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs +++ b/shadowsocks-csharp/Controller/Strategy/HighAvailabilityStrategy.cs @@ -132,14 +132,14 @@ namespace Shadowsocks.Controller.Strategy if (_currentServer == null || max.score - _currentServer.score > 200) { _currentServer = max; - Console.WriteLine("HA switching to server: {0}", _currentServer.server.FriendlyName()); + Logging.Info($"HA switching to server: {_currentServer.server.FriendlyName()}"); } } } public void UpdateLatency(Model.Server server, TimeSpan latency) { - Logging.Debug(String.Format("latency: {0} {1}", server.FriendlyName(), latency)); + Logging.Debug($"latency: {server.FriendlyName()} {latency}"); ServerStatus status; if (_serverStatus.TryGetValue(server, out status)) @@ -151,7 +151,7 @@ namespace Shadowsocks.Controller.Strategy public void UpdateLastRead(Model.Server server) { - Logging.Debug(String.Format("last read: {0}", server.FriendlyName())); + Logging.Debug($"last read: {server.FriendlyName()}"); ServerStatus status; if (_serverStatus.TryGetValue(server, out status)) @@ -162,7 +162,7 @@ namespace Shadowsocks.Controller.Strategy public void UpdateLastWrite(Model.Server server) { - Logging.Debug(String.Format("last write: {0}", server.FriendlyName())); + Logging.Debug($"last write: {server.FriendlyName()}"); ServerStatus status; if (_serverStatus.TryGetValue(server, out status)) @@ -173,7 +173,7 @@ namespace Shadowsocks.Controller.Strategy public void SetFailure(Model.Server server) { - Logging.Debug(String.Format("failure: {0}", server.FriendlyName())); + Logging.Debug($"failure: {server.FriendlyName()}"); ServerStatus status; if (_serverStatus.TryGetValue(server, out status)) diff --git a/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs b/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs index d3b5e26b..df34c994 100644 --- a/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs +++ b/shadowsocks-csharp/Controller/Strategy/StatisticsStrategy.cs @@ -4,7 +4,9 @@ using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Threading; + using Newtonsoft.Json; + using Shadowsocks.Model; namespace Shadowsocks.Controller.Strategy @@ -147,6 +149,5 @@ namespace Shadowsocks.Controller.Strategy { //TODO: combine this part of data with ICMP statics } - } } diff --git a/shadowsocks-csharp/Controller/System/AutoStartup.cs b/shadowsocks-csharp/Controller/System/AutoStartup.cs index 36ad775f..a2ad21c1 100644 --- a/shadowsocks-csharp/Controller/System/AutoStartup.cs +++ b/shadowsocks-csharp/Controller/System/AutoStartup.cs @@ -6,21 +6,23 @@ namespace Shadowsocks.Controller { class AutoStartup { + static string Key = "Shadowsocks_" + Application.StartupPath.GetHashCode(); + public static bool Set(bool enabled) { + RegistryKey runKey = null; try { string path = Application.ExecutablePath; - RegistryKey runKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); + runKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); if (enabled) { - runKey.SetValue("Shadowsocks", path); + runKey.SetValue(Key, path); } else { - runKey.DeleteValue("Shadowsocks"); + runKey.DeleteValue(Key); } - runKey.Close(); return true; } catch (Exception e) @@ -28,20 +30,39 @@ namespace Shadowsocks.Controller Logging.LogUsefulException(e); return false; } + finally + { + if (runKey != null) + { + try { runKey.Close(); } + catch (Exception e) + { Logging.LogUsefulException(e); } + } + } } public static bool Check() { + RegistryKey runKey = null; try { string path = Application.ExecutablePath; - RegistryKey runKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run"); + runKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true); string[] runList = runKey.GetValueNames(); - runKey.Close(); foreach (string item in runList) { - if (item.Equals("Shadowsocks")) + if (item.Equals(Key)) return true; + else if (item.Equals("Shadowsocks")) // Compatibility with older versions + { + string value = Convert.ToString(runKey.GetValue(item)); + if (path.Equals(value, StringComparison.InvariantCultureIgnoreCase)) + { + runKey.DeleteValue(item); + runKey.SetValue(Key, path); + return true; + } + } } return false; } @@ -50,6 +71,15 @@ namespace Shadowsocks.Controller Logging.LogUsefulException(e); return false; } + finally + { + if (runKey != null) + { + try { runKey.Close(); } + catch(Exception e) + { Logging.LogUsefulException(e); } + } + } } } } diff --git a/shadowsocks-csharp/Data/libsscrypto.dll.gz b/shadowsocks-csharp/Data/libsscrypto.dll.gz index b0429860..6c814b36 100755 Binary files a/shadowsocks-csharp/Data/libsscrypto.dll.gz and b/shadowsocks-csharp/Data/libsscrypto.dll.gz differ diff --git a/shadowsocks-csharp/Data/privoxy_conf.txt b/shadowsocks-csharp/Data/privoxy_conf.txt index 104b9626..98267fa6 100644 --- a/shadowsocks-csharp/Data/privoxy_conf.txt +++ b/shadowsocks-csharp/Data/privoxy_conf.txt @@ -1,5 +1,5 @@ -listen-address __POLIPO_BIND_IP__:8123 +listen-address __POLIPO_BIND_IP__:__POLIPO_BIND_PORT__ show-on-task-bar 0 activity-animation 0 -forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . -hide-console \ No newline at end of file +forward-socks5 / 127.0.0.1:__SOCKS_PORT__ . +hide-console diff --git a/shadowsocks-csharp/Encryption/EncryptorBase.cs b/shadowsocks-csharp/Encryption/EncryptorBase.cs index 8285f165..25694181 100644 --- a/shadowsocks-csharp/Encryption/EncryptorBase.cs +++ b/shadowsocks-csharp/Encryption/EncryptorBase.cs @@ -24,7 +24,7 @@ namespace Shadowsocks.Encryption protected byte[] GetPasswordHash() { byte[] inputBytes = Encoding.UTF8.GetBytes(Password); - byte[] hash = MD5.Create().ComputeHash(inputBytes); + byte[] hash = MbedTLS.MD5(inputBytes); return hash; } diff --git a/shadowsocks-csharp/Encryption/EncryptorFactory.cs b/shadowsocks-csharp/Encryption/EncryptorFactory.cs index f0e2d284..b9cda1a7 100644 --- a/shadowsocks-csharp/Encryption/EncryptorFactory.cs +++ b/shadowsocks-csharp/Encryption/EncryptorFactory.cs @@ -13,10 +13,6 @@ namespace Shadowsocks.Encryption static EncryptorFactory() { _registeredEncryptors = new Dictionary(); - foreach (string method in TableEncryptor.SupportedCiphers()) - { - _registeredEncryptors.Add(method, typeof(TableEncryptor)); - } foreach (string method in PolarSSLEncryptor.SupportedCiphers()) { _registeredEncryptors.Add(method, typeof(PolarSSLEncryptor)); @@ -31,7 +27,7 @@ namespace Shadowsocks.Encryption { if (string.IsNullOrEmpty(method)) { - method = "table"; + method = "aes-256-cfb"; } method = method.ToLowerInvariant(); Type t = _registeredEncryptors[method]; diff --git a/shadowsocks-csharp/Encryption/IVEncryptor.cs b/shadowsocks-csharp/Encryption/IVEncryptor.cs index 52d1970b..f1922ac0 100755 --- a/shadowsocks-csharp/Encryption/IVEncryptor.cs +++ b/shadowsocks-csharp/Encryption/IVEncryptor.cs @@ -62,18 +62,22 @@ namespace Shadowsocks.Encryption } keyLen = ciphers[_method][0]; ivLen = ciphers[_method][1]; - if (CachedKeys.ContainsKey(k)) + if (!CachedKeys.ContainsKey(k)) { - _key = CachedKeys[k]; - } - else - { - byte[] passbuf = Encoding.UTF8.GetBytes(password); - _key = new byte[32]; - byte[] iv = new byte[16]; - bytesToKey(passbuf, _key); - CachedKeys[k] = _key; + lock (CachedKeys) + { + if (!CachedKeys.ContainsKey(k)) + { + byte[] passbuf = Encoding.UTF8.GetBytes(password); + _key = new byte[32]; + byte[] iv = new byte[16]; + bytesToKey(passbuf, _key); + CachedKeys[k] = _key; + } + } } + if (_key == null) + _key = CachedKeys[k]; } protected void bytesToKey(byte[] password, byte[] key) @@ -83,16 +87,15 @@ namespace Shadowsocks.Encryption byte[] md5sum = null; while (i < key.Length) { - MD5 md5 = MD5.Create(); if (i == 0) { - md5sum = md5.ComputeHash(password); + md5sum = MbedTLS.MD5(password); } else { md5sum.CopyTo(result, 0); password.CopyTo(result, md5sum.Length); - md5sum = md5.ComputeHash(result); + md5sum = MbedTLS.MD5(result); } md5sum.CopyTo(key, i); i += md5sum.Length; diff --git a/shadowsocks-csharp/Encryption/MbedTLS.cs b/shadowsocks-csharp/Encryption/MbedTLS.cs new file mode 100644 index 00000000..a7162f66 --- /dev/null +++ b/shadowsocks-csharp/Encryption/MbedTLS.cs @@ -0,0 +1,65 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +using Shadowsocks.Controller; +using Shadowsocks.Properties; +using Shadowsocks.Util; + +namespace Shadowsocks.Encryption +{ + public class MbedTLS + { + const string DLLNAME = "libsscrypto"; + + static MbedTLS() + { + string dllPath = Utils.GetTempPath("libsscrypto.dll"); + try + { + FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); + } + catch (IOException) + { + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + LoadLibrary(dllPath); + } + + [DllImport("Kernel32.dll")] + private static extern IntPtr LoadLibrary(string path); + + public const int MD5_CTX_SIZE = 88; + + public static byte[] MD5(byte[] input) + { + IntPtr ctx = Marshal.AllocHGlobal(MD5_CTX_SIZE); + byte[] output = new byte[16]; + MbedTLS.md5_init(ctx); + MbedTLS.md5_starts(ctx); + MbedTLS.md5_update(ctx, input, (uint)input.Length); + MbedTLS.md5_finish(ctx, output); + MbedTLS.md5_free(ctx); + Marshal.FreeHGlobal(ctx); + return output; + } + + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public extern static void md5_init(IntPtr ctx); + + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public extern static void md5_free(IntPtr ctx); + + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public extern static void md5_starts(IntPtr ctx); + + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public extern static void md5_update(IntPtr ctx, byte[] input, uint ilen); + + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public extern static void md5_finish(IntPtr ctx, byte[] output); + } +} diff --git a/shadowsocks-csharp/Encryption/PolarSSL.cs b/shadowsocks-csharp/Encryption/PolarSSL.cs index 42ce5bf7..ddf59564 100755 --- a/shadowsocks-csharp/Encryption/PolarSSL.cs +++ b/shadowsocks-csharp/Encryption/PolarSSL.cs @@ -1,11 +1,10 @@ -using Shadowsocks.Controller; -using Shadowsocks.Properties; -using Shadowsocks.Util; -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Runtime.InteropServices; -using System.Text; + +using Shadowsocks.Controller; +using Shadowsocks.Properties; +using Shadowsocks.Util; namespace Shadowsocks.Encryption { @@ -19,8 +18,7 @@ namespace Shadowsocks.Encryption static PolarSSL() { - string tempPath = Utils.GetTempPath(); - string dllPath = tempPath + "/libsscrypto.dll"; + string dllPath = Utils.GetTempPath("libsscrypto.dll"); try { FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); @@ -30,7 +28,7 @@ namespace Shadowsocks.Encryption } catch (Exception e) { - Console.WriteLine(e.ToString()); + Logging.LogUsefulException(e); } LoadLibrary(dllPath); } @@ -63,6 +61,5 @@ namespace Shadowsocks.Encryption [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] public extern static int arc4_crypt(IntPtr ctx, int length, byte[] input, byte[] output); - } } diff --git a/shadowsocks-csharp/Encryption/PolarSSLEncryptor.cs b/shadowsocks-csharp/Encryption/PolarSSLEncryptor.cs index 3b3331f9..8e521862 100755 --- a/shadowsocks-csharp/Encryption/PolarSSLEncryptor.cs +++ b/shadowsocks-csharp/Encryption/PolarSSLEncryptor.cs @@ -26,7 +26,6 @@ namespace Shadowsocks.Encryption {"aes-128-cfb", new int[]{16, 16, CIPHER_AES, PolarSSL.AES_CTX_SIZE}}, {"aes-192-cfb", new int[]{24, 16, CIPHER_AES, PolarSSL.AES_CTX_SIZE}}, {"aes-256-cfb", new int[]{32, 16, CIPHER_AES, PolarSSL.AES_CTX_SIZE}}, - {"rc4", new int[]{16, 0, CIPHER_RC4, PolarSSL.ARC4_CTX_SIZE}}, {"rc4-md5", new int[]{16, 16, CIPHER_RC4, PolarSSL.ARC4_CTX_SIZE}}, }; @@ -61,7 +60,7 @@ namespace Shadowsocks.Encryption realkey = new byte[keyLen]; Array.Copy(_key, 0, temp, 0, keyLen); Array.Copy(iv, 0, temp, keyLen, ivLen); - realkey = MD5.Create().ComputeHash(temp); + realkey = MbedTLS.MD5(temp); } else { diff --git a/shadowsocks-csharp/Encryption/Sodium.cs b/shadowsocks-csharp/Encryption/Sodium.cs index 8d690dd2..3d20bdea 100755 --- a/shadowsocks-csharp/Encryption/Sodium.cs +++ b/shadowsocks-csharp/Encryption/Sodium.cs @@ -1,11 +1,10 @@ -using Shadowsocks.Controller; -using Shadowsocks.Properties; -using Shadowsocks.Util; -using System; -using System.Collections.Generic; +using System; using System.IO; using System.Runtime.InteropServices; -using System.Text; + +using Shadowsocks.Controller; +using Shadowsocks.Properties; +using Shadowsocks.Util; namespace Shadowsocks.Encryption { @@ -15,8 +14,7 @@ namespace Shadowsocks.Encryption static Sodium() { - string tempPath = Utils.GetTempPath(); - string dllPath = tempPath + "/libsscrypto.dll"; + string dllPath = Utils.GetTempPath("libsscrypto.dll"); try { FileManager.UncompressFile(dllPath, Resources.libsscrypto_dll); @@ -26,7 +24,7 @@ namespace Shadowsocks.Encryption } catch (Exception e) { - Console.WriteLine(e.ToString()); + Logging.LogUsefulException(e); } LoadLibrary(dllPath); } @@ -40,11 +38,13 @@ namespace Shadowsocks.Encryption [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] public extern static int crypto_stream_chacha20_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, ulong ic, byte[] k); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] + public extern static int crypto_stream_chacha20_ietf_xor_ic(byte[] c, byte[] m, ulong mlen, byte[] n, uint ic, byte[] k); + [DllImport(DLLNAME, CallingConvention = CallingConvention.Cdecl)] public extern static void ss_sha1_hmac_ex(byte[] key, uint keylen, byte[] input, int ioff, uint ilen, byte[] output); - } } diff --git a/shadowsocks-csharp/Encryption/SodiumEncryptor.cs b/shadowsocks-csharp/Encryption/SodiumEncryptor.cs index a18d2a69..b3877f86 100755 --- a/shadowsocks-csharp/Encryption/SodiumEncryptor.cs +++ b/shadowsocks-csharp/Encryption/SodiumEncryptor.cs @@ -10,6 +10,7 @@ namespace Shadowsocks.Encryption { const int CIPHER_SALSA20 = 1; const int CIPHER_CHACHA20 = 2; + const int CIPHER_CHACHA20_IETF = 3; const int SODIUM_BLOCK_SIZE = 64; @@ -29,6 +30,7 @@ namespace Shadowsocks.Encryption private static Dictionary _ciphers = new Dictionary { {"salsa20", new int[]{32, 8, CIPHER_SALSA20, PolarSSL.AES_CTX_SIZE}}, {"chacha20", new int[]{32, 8, CIPHER_CHACHA20, PolarSSL.AES_CTX_SIZE}}, + {"chacha20-ietf", new int[]{32, 12, CIPHER_CHACHA20_IETF, PolarSSL.AES_CTX_SIZE}}, }; protected override Dictionary getCiphers() @@ -75,6 +77,9 @@ namespace Shadowsocks.Encryption case CIPHER_CHACHA20: Sodium.crypto_stream_chacha20_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, ic, _key); break; + case CIPHER_CHACHA20_IETF: + Sodium.crypto_stream_chacha20_ietf_xor_ic(sodiumBuf, sodiumBuf, (ulong)(padding + length), iv, (uint)ic, _key); + break; } Buffer.BlockCopy(sodiumBuf, padding, outbuf, 0, length); padding += length; diff --git a/shadowsocks-csharp/Encryption/TableEncryptor.cs b/shadowsocks-csharp/Encryption/TableEncryptor.cs deleted file mode 100644 index 4b6c8fe3..00000000 --- a/shadowsocks-csharp/Encryption/TableEncryptor.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Shadowsocks.Encryption -{ - public class TableEncryptor - : EncryptorBase - { - public TableEncryptor(string method, string password, bool onetimeauth, bool isudp) - : base(method, password, onetimeauth, isudp) - { - byte[] hash = GetPasswordHash(); - // TODO endian - ulong a = BitConverter.ToUInt64(hash, 0); - for (int i = 0; i < 256; i++) - { - _encryptTable[i] = (byte)i; - } - for (int i = 1; i < 1024; i++) - { - _encryptTable = MergeSort(_encryptTable, a, i); - } - for (int i = 0; i < 256; i++) - { - _decryptTable[_encryptTable[i]] = (byte)i; - } - } - - public static List SupportedCiphers() - { - return new List(new string[]{"table"}); - } - - public override void Encrypt(byte[] buf, int length, byte[] outbuf, out int outlength) - { - byte[] result = new byte[length]; - for (int i = 0; i < length; i++) - { - outbuf[i] = _encryptTable[buf[i]]; - } - outlength = length; - } - - public override void Decrypt(byte[] buf, int length, byte[] outbuf, out int outlength) - { - byte[] result = new byte[length]; - for (int i = 0; i < length; i++) - { - outbuf[i] = _decryptTable[buf[i]]; - } - outlength = length; - } - - private readonly byte[] _encryptTable = new byte[256]; - private readonly byte[] _decryptTable = new byte[256]; - - private static long Compare(byte x, byte y, ulong a, int i) - { - return (long)(a % (ulong)(x + i)) - (long)(a % (ulong)(y + i)); - } - - private byte[] MergeSort(byte[] array, ulong a, int j) - { - if (array.Length == 1) - { - return array; - } - int middle = array.Length / 2; - byte[] left = new byte[middle]; - for (int i = 0; i < middle; i++) - { - left[i] = array[i]; - } - byte[] right = new byte[array.Length - middle]; - for (int i = 0; i < array.Length - middle; i++) - { - right[i] = array[i + middle]; - } - left = MergeSort(left, a, j); - right = MergeSort(right, a, j); - - int leftptr = 0; - int rightptr = 0; - - byte[] sorted = new byte[array.Length]; - for (int k = 0; k < array.Length; k++) - { - if (rightptr == right.Length || ((leftptr < left.Length) && (Compare(left[leftptr], right[rightptr], a, j) <= 0))) - { - sorted[k] = left[leftptr]; - leftptr++; - } - else if (leftptr == left.Length || ((rightptr < right.Length) && (Compare(right[rightptr], left[leftptr], a, j)) <= 0)) - { - sorted[k] = right[rightptr]; - rightptr++; - } - } - return sorted; - } - - public override void Dispose() - { - } - } -} diff --git a/shadowsocks-csharp/Model/Configuration.cs b/shadowsocks-csharp/Model/Configuration.cs index 58f9f941..d65c23a9 100755 --- a/shadowsocks-csharp/Model/Configuration.cs +++ b/shadowsocks-csharp/Model/Configuration.cs @@ -1,10 +1,9 @@ -using Shadowsocks.Controller; -using System; +using System; using System.Collections.Generic; using System.IO; -using System.Text; -using System.Windows.Forms; -using SimpleJson; + +using Shadowsocks.Controller; +using Newtonsoft.Json; namespace Shadowsocks.Model { @@ -32,13 +31,9 @@ namespace Shadowsocks.Model public Server GetCurrentServer() { if (index >= 0 && index < configs.Count) - { return configs[index]; - } else - { return GetDefaultServer(); - } } public static void CheckServer(Server server) @@ -53,27 +48,18 @@ namespace Shadowsocks.Model try { string configContent = File.ReadAllText(CONFIG_FILE); - Configuration config = SimpleJson.SimpleJson.DeserializeObject(configContent, new JsonSerializerStrategy()); + Configuration config = JsonConvert.DeserializeObject(configContent); config.isDefault = false; if (config.localPort == 0) - { config.localPort = 1080; - } - if (config.index == -1) - { - if (config.strategy == null) - { - config.index = 0; - } - } + if (config.index == -1 && config.strategy == null) + config.index = 0; return config; } catch (Exception e) { if (!(e is FileNotFoundException)) - { - Console.WriteLine(e); - } + Logging.LogUsefulException(e); return new Configuration { index = 0, @@ -91,26 +77,17 @@ namespace Shadowsocks.Model public static void Save(Configuration config) { if (config.index >= config.configs.Count) - { config.index = config.configs.Count - 1; - } if (config.index < -1) - { config.index = -1; - } - if (config.index == -1) - { - if (config.strategy == null) - { - config.index = 0; - } - } + if (config.index == -1 && config.strategy == null) + config.index = 0; config.isDefault = false; try { using (StreamWriter sw = new StreamWriter(File.Open(CONFIG_FILE, FileMode.Create))) { - string jsonString = SimpleJson.SimpleJson.SerializeObject(config); + string jsonString = JsonConvert.SerializeObject(config, Formatting.Indented); sw.Write(jsonString); sw.Flush(); } @@ -129,55 +106,32 @@ namespace Shadowsocks.Model private static void Assert(bool condition) { if (!condition) - { throw new Exception(I18N.GetString("assertion failure")); - } } public static void CheckPort(int port) { if (port <= 0 || port > 65535) - { throw new ArgumentException(I18N.GetString("Port out of range")); - } } public static void CheckLocalPort(int port) { CheckPort(port); if (port == 8123) - { throw new ArgumentException(I18N.GetString("Port can't be 8123")); - } } private static void CheckPassword(string password) { if (string.IsNullOrEmpty(password)) - { throw new ArgumentException(I18N.GetString("Password can not be blank")); - } } private static void CheckServer(string server) { if (string.IsNullOrEmpty(server)) - { throw new ArgumentException(I18N.GetString("Server IP can not be blank")); - } - } - - private class JsonSerializerStrategy : SimpleJson.PocoJsonSerializerStrategy - { - // convert string to int - public override object DeserializeObject(object value, Type type) - { - if (type == typeof(Int32) && value.GetType() == typeof(string)) - { - return Int32.Parse(value.ToString()); - } - return base.DeserializeObject(value, type); - } } } } diff --git a/shadowsocks-csharp/Model/LogViewerConfig.cs b/shadowsocks-csharp/Model/LogViewerConfig.cs index c82f1b22..4fe8f828 100644 --- a/shadowsocks-csharp/Model/LogViewerConfig.cs +++ b/shadowsocks-csharp/Model/LogViewerConfig.cs @@ -1,5 +1,7 @@ -using System; +using Shadowsocks.View; +using System; using System.Drawing; +using System.Windows.Forms; namespace Shadowsocks.Model { @@ -13,16 +15,37 @@ namespace Shadowsocks.Model public bool topMost; public bool wrapText; public bool toolbarShown; + public int width; + public int height; + public int top; + public int left; public LogViewerConfig() { - this.fontName = "Consolas"; - this.fontSize = 8; - this.bgColor = "black"; - this.textColor = "white"; - this.topMost = false; - this.wrapText = false; - this.toolbarShown = false; + fontName = "Consolas"; + fontSize = 8; + bgColor = "black"; + textColor = "white"; + topMost = false; + wrapText = false; + toolbarShown = false; + width = 600; + height = 400; + left = GetBestLeft(); + top = GetBestTop(); + } + + // Use GetBestTop() and GetBestLeft() to ensure the log viwer form can be always display IN screen. + public int GetBestLeft() + { + width = (width >= 400) ? width : 400; // set up the minimum size + return Screen.PrimaryScreen.WorkingArea.Width - width; + } + + public int GetBestTop() + { + height = (height >= 200) ? height : 200; // set up the minimum size + return Screen.PrimaryScreen.WorkingArea.Height - height; } public Font GetFont() diff --git a/shadowsocks-csharp/Model/Server.cs b/shadowsocks-csharp/Model/Server.cs index 55134335..8306d9e3 100755 --- a/shadowsocks-csharp/Model/Server.cs +++ b/shadowsocks-csharp/Model/Server.cs @@ -1,12 +1,9 @@ using System; -using System.Collections.Generic; using System.Text; -using System.IO; -using System.Diagnostics; -using SimpleJson; -using Shadowsocks.Controller; using System.Text.RegularExpressions; +using Shadowsocks.Controller; + namespace Shadowsocks.Model { [Serializable] @@ -17,7 +14,7 @@ namespace Shadowsocks.Model public string password; public string method; public string remarks; - public bool one_time_auth; + public bool auth; public override int GetHashCode() { @@ -27,7 +24,7 @@ namespace Shadowsocks.Model public override bool Equals(object obj) { Server o2 = (Server)obj; - return this.server == o2.server && this.server_port == o2.server_port; + return server == o2.server && server_port == o2.server_port; } public string FriendlyName() @@ -48,12 +45,12 @@ namespace Shadowsocks.Model public Server() { - this.server = ""; - this.server_port = 8388; - this.method = "aes-256-cfb"; - this.password = ""; - this.remarks = ""; - this.one_time_auth = false; + server = ""; + server_port = 8388; + method = "aes-256-cfb"; + password = ""; + remarks = ""; + auth = false; } public Server(string ssURL) : this() @@ -65,7 +62,7 @@ namespace Shadowsocks.Model { try { - bytes = System.Convert.FromBase64String(base64); + bytes = Convert.FromBase64String(base64); } catch (FormatException) { @@ -83,16 +80,15 @@ namespace Shadowsocks.Model string afterAt = data.Substring(indexLastAt + 1); int indexLastColon = afterAt.LastIndexOf(':'); - this.server_port = int.Parse(afterAt.Substring(indexLastColon + 1)); - this.server = afterAt.Substring(0, indexLastColon); + server_port = int.Parse(afterAt.Substring(indexLastColon + 1)); + server = afterAt.Substring(0, indexLastColon); string beforeAt = data.Substring(0, indexLastAt); string[] parts = beforeAt.Split(new[] { ':' }); - this.method = parts[0]; - this.password = parts[1]; + method = parts[0]; + password = parts[1]; //TODO: read one_time_auth - } catch (IndexOutOfRangeException) { diff --git a/shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs b/shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs index 62a48c2e..70433117 100644 --- a/shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs +++ b/shadowsocks-csharp/Model/StatisticsStrategyConfiguration.cs @@ -3,17 +3,17 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; -using Shadowsocks.Controller; -using Shadowsocks.Controller.Strategy; -using SimpleJson; + using Newtonsoft.Json; +using Shadowsocks.Controller; + namespace Shadowsocks.Model { [Serializable] public class StatisticsStrategyConfiguration { - public static readonly string ID = "com.shadowsocks.strategy.statistics"; + public static readonly string ID = "com.shadowsocks.strategy.statistics"; private bool _statisticsEnabled = true; private bool _byIsp = false; private bool _byHourOfDay = false; @@ -21,7 +21,6 @@ namespace Shadowsocks.Model private int _dataCollectionMinutes = 10; private int _repeatTimesNum = 4; - private const string ConfigFile = "statistics-config.json"; public static StatisticsStrategyConfiguration Load() @@ -32,7 +31,7 @@ namespace Shadowsocks.Model var configuration = JsonConvert.DeserializeObject(content); return configuration; } - catch (FileNotFoundException e) + catch (FileNotFoundException) { var configuration = new StatisticsStrategyConfiguration(); Save(configuration); @@ -62,10 +61,10 @@ namespace Shadowsocks.Model public StatisticsStrategyConfiguration() { - var availabilityStatisticsType = typeof (AvailabilityStatistics); + var availabilityStatisticsType = typeof(AvailabilityStatistics); var statisticsData = availabilityStatisticsType.GetNestedType("StatisticsData"); var properties = statisticsData.GetFields(BindingFlags.Instance | BindingFlags.Public); - Calculations = properties.ToDictionary(p => p.Name, _ => (float) 0); + Calculations = properties.ToDictionary(p => p.Name, _ => (float)0); } public bool StatisticsEnabled diff --git a/shadowsocks-csharp/Program.cs b/shadowsocks-csharp/Program.cs index cb40cb23..977ebffd 100755 --- a/shadowsocks-csharp/Program.cs +++ b/shadowsocks-csharp/Program.cs @@ -1,13 +1,13 @@ -using Shadowsocks.Controller; -using Shadowsocks.Properties; -using Shadowsocks.View; -using System; -using System.Collections.Generic; +using System; using System.Diagnostics; using System.IO; using System.Threading; using System.Windows.Forms; +using Shadowsocks.Controller; +using Shadowsocks.Util; +using Shadowsocks.View; + namespace Shadowsocks { static class Program @@ -18,7 +18,7 @@ namespace Shadowsocks [STAThread] static void Main() { - Util.Utils.ReleaseMemory(true); + Utils.ReleaseMemory(true); using (Mutex mutex = new Mutex(false, "Global\\Shadowsocks_" + Application.StartupPath.GetHashCode())) { Application.EnableVisualStyles(); @@ -37,15 +37,19 @@ namespace Shadowsocks return; } Directory.SetCurrentDirectory(Application.StartupPath); -#if !DEBUG +#if DEBUG + Logging.OpenLogFile(); + + // truncate privoxy log file while debugging + string privoxyLogFilename = Utils.GetTempPath("privoxy.log"); + if (File.Exists(privoxyLogFilename)) + using (new FileStream(privoxyLogFilename, FileMode.Truncate)) { } +#else Logging.OpenLogFile(); #endif ShadowsocksController controller = new ShadowsocksController(); - MenuViewController viewController = new MenuViewController(controller); - controller.Start(); - Application.Run(); } } diff --git a/shadowsocks-csharp/Util/Util.cs b/shadowsocks-csharp/Util/Util.cs index 1858ee90..178c994b 100755 --- a/shadowsocks-csharp/Util/Util.cs +++ b/shadowsocks-csharp/Util/Util.cs @@ -1,32 +1,48 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Runtime.InteropServices; -using System.Text; using System.Windows.Forms; +using Shadowsocks.Controller; + namespace Shadowsocks.Util { public class Utils { + private static string TempPath = null; + // return path to store temporary files public static string GetTempPath() { - if (File.Exists(Application.StartupPath + "\\shadowsocks_portable_mode.txt")) + if (TempPath == null) { - try - { - Directory.CreateDirectory(Application.StartupPath + "\\temp"); - } catch (Exception e) - { - Console.WriteLine(e); - } - // don't use "/", it will fail when we call explorer /select xxx/temp\xxx.log - return Application.StartupPath + "\\temp"; + if (File.Exists(Path.Combine(Application.StartupPath, "shadowsocks_portable_mode.txt"))) + try + { + Directory.CreateDirectory(Path.Combine(Application.StartupPath, "temp")); + } + catch (Exception e) + { + TempPath = Path.GetTempPath(); + Logging.LogUsefulException(e); + } + finally + { + // don't use "/", it will fail when we call explorer /select xxx/temp\xxx.log + TempPath = Path.Combine(Application.StartupPath, "temp"); + } + else + TempPath = Path.GetTempPath(); } - return Path.GetTempPath(); + return TempPath; + } + + // return a full path with filename combined which pointed to the temporary directory + public static string GetTempPath(string filename) + { + return Path.Combine(GetTempPath(), filename); } public static void ReleaseMemory(bool removePages) @@ -82,6 +98,33 @@ namespace Shadowsocks.Util } } + public static string FormatBandwidth(long n) + { + float f = n; + string unit = "B"; + if (f > 1024) + { + f = f / 1024; + unit = "KiB"; + } + if (f > 1024) + { + f = f / 1024; + unit = "MiB"; + } + if (f > 1024) + { + f = f / 1024; + unit = "GiB"; + } + if (f > 1024) + { + f = f / 1024; + unit = "TiB"; + } + return $"{f:0.##}{unit}"; + } + [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetProcessWorkingSetSize(IntPtr process, diff --git a/shadowsocks-csharp/View/ConfigForm.Designer.cs b/shadowsocks-csharp/View/ConfigForm.Designer.cs index 8c5334d0..fcd11838 100755 --- a/shadowsocks-csharp/View/ConfigForm.Designer.cs +++ b/shadowsocks-csharp/View/ConfigForm.Designer.cs @@ -198,14 +198,13 @@ this.EncryptionSelect.ImeMode = System.Windows.Forms.ImeMode.NoControl; this.EncryptionSelect.ItemHeight = 12; this.EncryptionSelect.Items.AddRange(new object[] { - "table", "rc4-md5", "salsa20", "chacha20", + "chacha20-ietf", "aes-256-cfb", "aes-192-cfb", - "aes-128-cfb", - "rc4"}); + "aes-128-cfb"}); this.EncryptionSelect.Location = new System.Drawing.Point(83, 87); this.EncryptionSelect.Name = "EncryptionSelect"; this.EncryptionSelect.Size = new System.Drawing.Size(160, 20); diff --git a/shadowsocks-csharp/View/ConfigForm.cs b/shadowsocks-csharp/View/ConfigForm.cs index 9ae26583..0135f48d 100755 --- a/shadowsocks-csharp/View/ConfigForm.cs +++ b/shadowsocks-csharp/View/ConfigForm.cs @@ -79,12 +79,12 @@ namespace Shadowsocks.View } Server server = new Server { - server = IPTextBox.Text, + server = IPTextBox.Text.Trim(), server_port = int.Parse(ServerPortTextBox.Text), password = PasswordTextBox.Text, method = EncryptionSelect.Text, remarks = RemarksTextBox.Text, - one_time_auth = OneTimeAuth.Checked + auth = OneTimeAuth.Checked }; int localPort = int.Parse(ProxyPortTextBox.Text); Configuration.CheckServer(server); @@ -117,7 +117,7 @@ namespace Shadowsocks.View ProxyPortTextBox.Text = _modifiedConfiguration.localPort.ToString(); EncryptionSelect.Text = server.method ?? "aes-256-cfb"; RemarksTextBox.Text = server.remarks; - OneTimeAuth.Checked = server.one_time_auth; + OneTimeAuth.Checked = server.auth; } } @@ -188,7 +188,10 @@ namespace Shadowsocks.View ServersListBox.SelectedIndex = _lastSelectedIndex; return; } - ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].FriendlyName(); + if (_lastSelectedIndex >= 0) + { + ServersListBox.Items[_lastSelectedIndex] = _modifiedConfiguration.configs[_lastSelectedIndex].FriendlyName(); + } UpdateMoveUpAndDownButton(); LoadSelectedServer(); _lastSelectedIndex = ServersListBox.SelectedIndex; diff --git a/shadowsocks-csharp/View/LogForm.Designer.cs b/shadowsocks-csharp/View/LogForm.Designer.cs index a4fcfad1..3ac114ba 100644 --- a/shadowsocks-csharp/View/LogForm.Designer.cs +++ b/shadowsocks-csharp/View/LogForm.Designer.cs @@ -57,13 +57,13 @@ this.LogMessageTextBox.Dock = System.Windows.Forms.DockStyle.Fill; this.LogMessageTextBox.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.LogMessageTextBox.ForeColor = System.Drawing.Color.White; - this.LogMessageTextBox.Location = new System.Drawing.Point(3, 38); + this.LogMessageTextBox.Location = new System.Drawing.Point(3, 40); this.LogMessageTextBox.MaxLength = 2147483647; this.LogMessageTextBox.Multiline = true; this.LogMessageTextBox.Name = "LogMessageTextBox"; this.LogMessageTextBox.ReadOnly = true; this.LogMessageTextBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.LogMessageTextBox.Size = new System.Drawing.Size(584, 377); + this.LogMessageTextBox.Size = new System.Drawing.Size(378, 131); this.LogMessageTextBox.TabIndex = 0; // // MainMenu @@ -144,9 +144,9 @@ this.TopMostCheckBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left))); this.TopMostCheckBox.AutoSize = true; - this.TopMostCheckBox.Location = new System.Drawing.Point(249, 3); + this.TopMostCheckBox.Location = new System.Drawing.Point(247, 3); this.TopMostCheckBox.Name = "TopMostCheckBox"; - this.TopMostCheckBox.Size = new System.Drawing.Size(72, 23); + this.TopMostCheckBox.Size = new System.Drawing.Size(71, 25); this.TopMostCheckBox.TabIndex = 3; this.TopMostCheckBox.Text = "&Top Most"; this.TopMostCheckBox.UseVisualStyleBackColor = true; @@ -157,7 +157,7 @@ this.ChangeFontButton.AutoSize = true; this.ChangeFontButton.Location = new System.Drawing.Point(84, 3); this.ChangeFontButton.Name = "ChangeFontButton"; - this.ChangeFontButton.Size = new System.Drawing.Size(75, 23); + this.ChangeFontButton.Size = new System.Drawing.Size(75, 25); this.ChangeFontButton.TabIndex = 2; this.ChangeFontButton.Text = "&Font"; this.ChangeFontButton.UseVisualStyleBackColor = true; @@ -168,7 +168,7 @@ this.CleanLogsButton.AutoSize = true; this.CleanLogsButton.Location = new System.Drawing.Point(3, 3); this.CleanLogsButton.Name = "CleanLogsButton"; - this.CleanLogsButton.Size = new System.Drawing.Size(75, 23); + this.CleanLogsButton.Size = new System.Drawing.Size(75, 25); this.CleanLogsButton.TabIndex = 1; this.CleanLogsButton.Text = "&Clean Logs"; this.CleanLogsButton.UseVisualStyleBackColor = true; @@ -181,7 +181,7 @@ this.WrapTextCheckBox.AutoSize = true; this.WrapTextCheckBox.Location = new System.Drawing.Point(165, 3); this.WrapTextCheckBox.Name = "WrapTextCheckBox"; - this.WrapTextCheckBox.Size = new System.Drawing.Size(78, 23); + this.WrapTextCheckBox.Size = new System.Drawing.Size(76, 25); this.WrapTextCheckBox.TabIndex = 0; this.WrapTextCheckBox.Text = "&Wrap Text"; this.WrapTextCheckBox.UseVisualStyleBackColor = true; @@ -199,7 +199,7 @@ this.tableLayoutPanel1.RowCount = 2; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel1.Size = new System.Drawing.Size(590, 418); + this.tableLayoutPanel1.Size = new System.Drawing.Size(384, 174); this.tableLayoutPanel1.TabIndex = 2; // // ToolbarFlowLayoutPanel @@ -212,16 +212,17 @@ this.ToolbarFlowLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill; this.ToolbarFlowLayoutPanel.Location = new System.Drawing.Point(3, 3); this.ToolbarFlowLayoutPanel.Name = "ToolbarFlowLayoutPanel"; - this.ToolbarFlowLayoutPanel.Size = new System.Drawing.Size(584, 29); + this.ToolbarFlowLayoutPanel.Size = new System.Drawing.Size(378, 31); this.ToolbarFlowLayoutPanel.TabIndex = 2; // // LogForm // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(590, 418); + this.ClientSize = new System.Drawing.Size(384, 174); this.Controls.Add(this.tableLayoutPanel1); this.Menu = this.MainMenu; + this.MinimumSize = new System.Drawing.Size(400, 213); this.Name = "LogForm"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.Text = "Log Viewer"; diff --git a/shadowsocks-csharp/View/LogForm.cs b/shadowsocks-csharp/View/LogForm.cs index e2bf9fdd..d49cd1af 100644 --- a/shadowsocks-csharp/View/LogForm.cs +++ b/shadowsocks-csharp/View/LogForm.cs @@ -1,16 +1,12 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; using System.Drawing; using System.IO; -using System.Linq; -using System.Text; using System.Windows.Forms; using Shadowsocks.Controller; using Shadowsocks.Properties; using Shadowsocks.Model; +using Shadowsocks.Util; namespace Shadowsocks.View { @@ -27,17 +23,21 @@ namespace Shadowsocks.View this.controller = controller; this.filename = filename; InitializeComponent(); - this.Icon = Icon.FromHandle(Resources.ssw128.GetHicon()); + Icon = Icon.FromHandle(Resources.ssw128.GetHicon()); LogViewerConfig config = controller.GetConfigurationCopy().logViewer; if (config == null) + { config = new LogViewerConfig(); - topMostTrigger = config.topMost; - wrapTextTrigger = config.wrapText; - toolbarTrigger = config.toolbarShown; - LogMessageTextBox.BackColor = config.GetBackgroundColor(); - LogMessageTextBox.ForeColor = config.GetTextColor(); - LogMessageTextBox.Font = config.GetFont(); + } + else { + topMostTrigger = config.topMost; + wrapTextTrigger = config.wrapText; + toolbarTrigger = config.toolbarShown; + LogMessageTextBox.BackColor = config.GetBackgroundColor(); + LogMessageTextBox.ForeColor = config.GetTextColor(); + LogMessageTextBox.Font = config.GetFont(); + } UpdateTexts(); } @@ -57,7 +57,7 @@ namespace Shadowsocks.View WrapTextMenuItem.Text = I18N.GetString("&Wrap Text"); TopMostMenuItem.Text = I18N.GetString("&Top Most"); ShowToolbarMenuItem.Text = I18N.GetString("&Show Toolbar"); - this.Text = I18N.GetString("Log Viewer"); + Text = I18N.GetString("Log Viewer"); } private void Timer_Tick(object sender, EventArgs e) @@ -108,18 +108,30 @@ namespace Shadowsocks.View lastOffset = reader.BaseStream.Position; } + + this.Text = I18N.GetString("Log Viewer") + + $" [in: {Utils.FormatBandwidth(controller.inboundCounter)}, out: {Utils.FormatBandwidth(controller.outboundCounter)}]"; } private void LogForm_Load(object sender, EventArgs e) { InitContent(); + timer = new Timer(); timer.Interval = 300; timer.Tick += Timer_Tick; timer.Start(); + LogViewerConfig config = controller.GetConfigurationCopy().logViewer; + if (config == null) + config = new LogViewerConfig(); + Height = config.height; + Width = config.width; + Top = config.GetBestTop(); + Left = config.GetBestLeft(); + topMostTriggerLock = true; - this.TopMost = TopMostMenuItem.Checked = TopMostCheckBox.Checked = topMostTrigger; + TopMost = TopMostMenuItem.Checked = TopMostCheckBox.Checked = topMostTrigger; topMostTriggerLock = false; wrapTextTriggerLock = true; @@ -141,19 +153,23 @@ namespace Shadowsocks.View config.SetFont(LogMessageTextBox.Font); config.SetBackgroundColor(LogMessageTextBox.BackColor); config.SetTextColor(LogMessageTextBox.ForeColor); + config.top = Top; + config.left = Left; + config.height = Height; + config.width = Width; controller.SaveLogViewerConfig(config); } private void OpenLocationMenuItem_Click(object sender, EventArgs e) { string argument = "/select, \"" + filename + "\""; - Console.WriteLine(argument); + Logging.Debug(argument); System.Diagnostics.Process.Start("explorer.exe", argument); } private void ExitMenuItem_Click(object sender, EventArgs e) { - this.Close(); + Close(); } private void LogForm_Shown(object sender, EventArgs e) @@ -208,7 +224,7 @@ namespace Shadowsocks.View } #endregion - #region Trigger the log messages wrapable, or not. + #region Trigger the log messages to wrapable, or not. bool wrapTextTrigger = false; bool wrapTextTriggerLock = false; @@ -241,7 +257,7 @@ namespace Shadowsocks.View } #endregion - #region Trigger this window top most, or not. + #region Trigger the window to top most, or not. bool topMostTrigger = false; bool topMostTriggerLock = false; @@ -250,7 +266,7 @@ namespace Shadowsocks.View topMostTriggerLock = true; topMostTrigger = !topMostTrigger; - this.TopMost = topMostTrigger; + TopMost = topMostTrigger; TopMostMenuItem.Checked = TopMostCheckBox.Checked = topMostTrigger; topMostTriggerLock = false; diff --git a/shadowsocks-csharp/View/LogForm.resx b/shadowsocks-csharp/View/LogForm.resx index e8bf04bf..c921ecfb 100644 --- a/shadowsocks-csharp/View/LogForm.resx +++ b/shadowsocks-csharp/View/LogForm.resx @@ -117,7 +117,31 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + True + 17, 17 + + True + + + True + + + True + + + True + + + True + + + True + + + True + \ No newline at end of file diff --git a/shadowsocks-csharp/View/MenuViewController.cs b/shadowsocks-csharp/View/MenuViewController.cs index b19a244f..32eb992d 100755 --- a/shadowsocks-csharp/View/MenuViewController.cs +++ b/shadowsocks-csharp/View/MenuViewController.cs @@ -1,16 +1,18 @@ -using Shadowsocks.Controller; -using Shadowsocks.Model; -using Shadowsocks.Properties; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; -using System.Text; using System.Windows.Forms; + using ZXing; using ZXing.Common; using ZXing.QrCode; +using Shadowsocks.Controller; +using Shadowsocks.Model; +using Shadowsocks.Properties; +using Shadowsocks.Util; + namespace Shadowsocks.View { public class MenuViewController @@ -44,6 +46,8 @@ namespace Shadowsocks.View private MenuItem editOnlinePACItem; private MenuItem autoCheckUpdatesToggleItem; private ConfigForm configForm; + private List logForms = new List(); + private bool logFormsVisible = false; private string _urlToOpen; public MenuViewController(ShadowsocksController controller) @@ -66,7 +70,10 @@ namespace Shadowsocks.View UpdateTrayIcon(); _notifyIcon.Visible = true; _notifyIcon.ContextMenu = contextMenu1; + _notifyIcon.BalloonTipClicked += notifyIcon1_BalloonTipClicked; + _notifyIcon.MouseClick += notifyIcon1_Click; _notifyIcon.MouseDoubleClick += notifyIcon1_DoubleClick; + _notifyIcon.BalloonTipClosed += _notifyIcon_BalloonTipClosed; this.updateChecker = new UpdateChecker(); updateChecker.CheckUpdateCompleted += updateChecker_CheckUpdateCompleted; @@ -78,7 +85,7 @@ namespace Shadowsocks.View if (config.autoCheckUpdate) { _isStartupChecking = true; - updateChecker.CheckUpdate(config); + updateChecker.CheckUpdate(config, 3000); } if (config.isDefault) @@ -200,7 +207,6 @@ namespace Shadowsocks.View }); } - private void controller_ConfigChanged(object sender, EventArgs e) { LoadCurrentConfiguration(); @@ -256,7 +262,6 @@ namespace Shadowsocks.View if (updateChecker.NewVersionFound) { ShowBalloonTip(String.Format(I18N.GetString("Shadowsocks {0} Update Found"), updateChecker.LatestVersionNumber), I18N.GetString("Click here to update"), ToolTipIcon.Info, 5000); - _notifyIcon.BalloonTipClicked += notifyIcon1_BalloonTipClicked; _isFirstRun = false; } else if (!_isStartupChecking) @@ -269,11 +274,24 @@ namespace Shadowsocks.View void notifyIcon1_BalloonTipClicked(object sender, EventArgs e) { - _notifyIcon.BalloonTipClicked -= notifyIcon1_BalloonTipClicked; - string argument = "/select, \"" + updateChecker.LatestVersionLocalName + "\""; - System.Diagnostics.Process.Start("explorer.exe", argument); + if (updateChecker.NewVersionFound) + { + updateChecker.NewVersionFound = false; /* Reset the flag */ + if (System.IO.File.Exists(updateChecker.LatestVersionLocalName)) + { + string argument = "/select, \"" + updateChecker.LatestVersionLocalName + "\""; + System.Diagnostics.Process.Start("explorer.exe", argument); + } + } } + private void _notifyIcon_BalloonTipClosed(object sender, EventArgs e) + { + if (updateChecker.NewVersionFound) + { + updateChecker.NewVersionFound = false; /* Reset the flag */ + } + } private void LoadCurrentConfiguration() { @@ -324,7 +342,6 @@ namespace Shadowsocks.View { item.Checked = true; } - } } @@ -342,10 +359,36 @@ namespace Shadowsocks.View } } + private void ShowLogForms() + { + if (logForms.Count == 0) + { + LogForm f = new LogForm(controller, Logging.LogFilePath); + f.Show(); + f.FormClosed += logForm_FormClosed; + + logForms.Add(f); + logFormsVisible = true; + } + else + { + logFormsVisible = !logFormsVisible; + foreach (LogForm f in logForms) + { + f.Visible = logFormsVisible; + } + } + } + + void logForm_FormClosed(object sender, FormClosedEventArgs e) + { + logForms.Remove((LogForm)sender); + } + void configForm_FormClosed(object sender, FormClosedEventArgs e) { configForm = null; - Util.Utils.ReleaseMemory(true); + Utils.ReleaseMemory(true); ShowFirstTimeBalloon(); } @@ -378,6 +421,18 @@ namespace Shadowsocks.View Process.Start("https://github.com/shadowsocks/shadowsocks-windows"); } + private void notifyIcon1_Click(object sender, MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + // TODO: show something interesting + } + else if (e.Button == MouseButtons.Middle) + { + ShowLogForms(); + } + } + private void notifyIcon1_DoubleClick(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Left) @@ -436,11 +491,13 @@ namespace Shadowsocks.View private void ShowLogItem_Click(object sender, EventArgs e) { - string argument = Logging.LogFile; + LogForm f = new LogForm(controller, Logging.LogFilePath); + f.Show(); + f.FormClosed += logForm_FormClosed; - new LogForm(controller, argument).Show(); + logForms.Add(f); } - + private void StatisticsConfigItem_Click(object sender, EventArgs e) { StatisticsStrategyConfigurationForm form = new StatisticsStrategyConfigurationForm(controller); @@ -549,9 +606,11 @@ namespace Shadowsocks.View Process.Start(_urlToOpen); } - private void AutoStartupItem_Click(object sender, EventArgs e) { + private void AutoStartupItem_Click(object sender, EventArgs e) + { AutoStartupItem.Checked = !AutoStartupItem.Checked; - if (!AutoStartup.Set(AutoStartupItem.Checked)) { + if (!AutoStartup.Set(AutoStartupItem.Checked)) + { MessageBox.Show(I18N.GetString("Failed to update registry")); } } diff --git a/shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs b/shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs index 88e32309..aae33546 100644 --- a/shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs +++ b/shadowsocks-csharp/View/StatisticsStrategyConfigurationForm.cs @@ -2,16 +2,15 @@ using System.Collections.Generic; using System.Data; using System.Linq; +using System.Net.NetworkInformation; using System.Windows.Forms; -using System.Windows.Forms.DataVisualization.Charting; + using Shadowsocks.Controller; using Shadowsocks.Model; -using SimpleJson; -using System.Net.NetworkInformation; namespace Shadowsocks.View { - public partial class StatisticsStrategyConfigurationForm: Form + public partial class StatisticsStrategyConfigurationForm : Form { private readonly ShadowsocksController _controller; private StatisticsStrategyConfiguration _configuration; @@ -51,9 +50,9 @@ namespace Shadowsocks.View serverSelector.DataSource = _servers; - _dataTable.Columns.Add("Timestamp", typeof (DateTime)); - _dataTable.Columns.Add("Package Loss", typeof (int)); - _dataTable.Columns.Add("Ping", typeof (int)); + _dataTable.Columns.Add("Timestamp", typeof(DateTime)); + _dataTable.Columns.Add("Package Loss", typeof(int)); + _dataTable.Columns.Add("Ping", typeof(int)); StatisticsChart.Series["Package Loss"].XValueMember = "Timestamp"; StatisticsChart.Series["Package Loss"].YValueMembers = "Package Loss"; @@ -64,7 +63,6 @@ namespace Shadowsocks.View StatisticsChart.DataBind(); } - private void CancelButton_Click(object sender, EventArgs e) { Close(); @@ -85,6 +83,9 @@ namespace Shadowsocks.View { string serverName = _servers[serverSelector.SelectedIndex]; _dataTable.Rows.Clear(); + + //return directly when no data is usable + if (_controller.availabilityStatistics?.FilteredStatistics == null) return; List statistics; if (!_controller.availabilityStatistics.FilteredStatistics.TryGetValue(serverName, out statistics)) return; IEnumerable> dataGroups; diff --git a/shadowsocks-csharp/app.config b/shadowsocks-csharp/app.config index a7ba1069..c96d7c75 100755 --- a/shadowsocks-csharp/app.config +++ b/shadowsocks-csharp/app.config @@ -8,11 +8,11 @@ - + - + diff --git a/shadowsocks-csharp/packages.config b/shadowsocks-csharp/packages.config index b309fb97..2230519e 100644 --- a/shadowsocks-csharp/packages.config +++ b/shadowsocks-csharp/packages.config @@ -1,9 +1,11 @@ - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/shadowsocks-csharp/shadowsocks-csharp.csproj b/shadowsocks-csharp/shadowsocks-csharp.csproj index 423268d8..69f7b546 100644 --- a/shadowsocks-csharp/shadowsocks-csharp.csproj +++ b/shadowsocks-csharp/shadowsocks-csharp.csproj @@ -48,6 +48,7 @@ prompt ManagedMinimumRules.ruleset false + false bin\x86\Release\ @@ -68,21 +69,18 @@ 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll True - False 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll True - False 3rd\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll True - False - - 3rd\Newtonsoft.Json.7.0.1\lib\net40\Newtonsoft.Json.dll + + 3rd\Newtonsoft.Json.8.0.2\lib\net40\Newtonsoft.Json.dll True @@ -90,22 +88,26 @@ - - 3rd\Microsoft.Bcl.1.1.8\lib\net40\System.IO.dll + + 3rd\Microsoft.Bcl.1.1.10\lib\net40\System.IO.dll True - False - - - 3rd\Microsoft.Bcl.1.1.8\lib\net40\System.Runtime.dll + + 3rd\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll True - False - - 3rd\Microsoft.Bcl.1.1.8\lib\net40\System.Threading.Tasks.dll + + 3rd\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + True + + + 3rd\Microsoft.Bcl.1.1.10\lib\net40\System.Runtime.dll + True + + + 3rd\Microsoft.Bcl.1.1.10\lib\net40\System.Threading.Tasks.dll True - False @@ -162,7 +164,6 @@ - @@ -184,11 +185,11 @@ + - @@ -323,18 +324,15 @@ - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + + +