1 // OpenTween - Client of Twitter
2 // Copyright (c) 2007-2011 kiri_feather (@kiri_feather) <kiri.feather@gmail.com>
3 // (c) 2008-2011 Moz (@syo68k)
4 // (c) 2008-2011 takeshik (@takeshik) <http://www.takeshik.org/>
5 // (c) 2010-2011 anis774 (@anis774) <http://d.hatena.ne.jp/anis774/>
6 // (c) 2010-2011 fantasticswallow (@f_swallow) <http://twitter.com/f_swallow>
7 // (c) 2011 Egtra (@egtra) <http://dev.activebasic.com/egtra/>
8 // (c) 2011 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
9 // All rights reserved.
11 // This file is part of OpenTween.
13 // This program is free software; you can redistribute it and/or modify it
14 // under the terms of the GNU General public License as published by the Free
15 // Software Foundation; either version 3 of the License, or (at your option)
18 // This program is distributed in the hope that it will be useful, but
19 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
23 // You should have received a copy of the GNU General public License along
24 // with this program. if not, see <http://www.gnu.org/licenses/>, or write to
25 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
26 // Boston, MA 02110-1301, USA.
29 using System.Collections.Generic;
33 using System.Windows.Forms;
35 using System.Globalization;
36 using System.Security.Cryptography;
38 using System.Drawing.Imaging;
39 using System.Collections;
40 using System.Security.Principal;
41 using System.Runtime.Serialization.Json;
42 using System.Reflection;
43 using System.Diagnostics;
44 using System.Text.RegularExpressions;
46 using System.Net.Http;
47 using System.Net.NetworkInformation;
48 using System.Runtime.InteropServices;
50 using OpenTween.Models;
51 using OpenTween.Setting;
55 public static class MyCommon
57 private static readonly object LockObj = new object();
58 public static bool _endingFlag; //終了フラグ
59 public static string settingPath;
70 public enum NameBalloonEnum
77 public enum DispTitleEnum
89 public enum LogUnitEnum
96 public enum UploadFileType
103 public enum UrlConverter
117 public enum HITRESULT
126 public enum HttpTimeOut
133 //Backgroundworkerへ処理種別を通知するための引数用enum
134 public enum WORKERTYPE
138 DirectMessegeRcv, //受信DM取得
139 DirectMessegeSnt, //送信DM取得
140 PostMessage, //発言POST
143 Follower, //Followerリスト取得
149 UserStream, //UserStream
150 UserTimeline, //UserTimeline
151 BlockIds, //Blocking/ids
152 Configuration, //Twitter Configuration読み込み
153 NoRetweetIds, //RT非表示ユーザー取得
155 ErrorState, //エラー表示のみで後処理終了(認証エラー時など)
158 public static class DEFAULTTAB
160 public const string RECENT = "Recent";
161 public const string REPLY = "Reply";
162 public const string DM = "Direct";
163 public const string FAV = "Favorites";
164 public static readonly string MUTE = Properties.Resources.MuteTabName;
166 //private string dummy;
168 //private object ReferenceEquals()
170 // return new object();
172 //private object Equals()
174 // return new object();
178 public static readonly object Block = null;
179 public static bool TraceFlag = false;
182 public static bool DebugBuild = true;
184 public static bool DebugBuild = false;
187 public enum ACCOUNT_STATE
193 public enum REPLY_ICONSTATE
201 public enum EVENTTYPE
208 ListMemberRemoved = 16,
216 ListUserSubscribed = 4096,
217 ListUserUnsubscribed = 8192,
218 ListDestroyed = 16384,
221 QuotedTweet = 131072,
224 All = (None | Favorite | Unfavorite | Follow | ListMemberAdded | ListMemberRemoved |
225 Block | Unblock | UserUpdate | Deleted | ListCreated | ListUpdated | Unfollow |
226 ListUserSubscribed | ListUserUnsubscribed | ListDestroyed |
227 Mute | Unmute | QuotedTweet | Retweet),
230 public static _Assembly EntryAssembly { get; internal set; }
231 public static string FileVersion { get; internal set; }
235 var assembly = Assembly.GetExecutingAssembly();
236 MyCommon.EntryAssembly = assembly;
238 var fileVersionAttribute = (AssemblyFileVersionAttribute)assembly
239 .GetCustomAttributes(typeof(AssemblyFileVersionAttribute)).First();
240 MyCommon.FileVersion = fileVersionAttribute.Version;
243 public static string GetErrorLogPath()
245 return Path.Combine(Path.GetDirectoryName(MyCommon.EntryAssembly.Location), "ErrorLogs");
248 public static void TraceOut(WebApiException ex)
250 var message = ExceptionOutMessage(ex);
252 if (ex.ResponseText != null)
253 message += Environment.NewLine + "------- Response Data -------" + Environment.NewLine + ex.ResponseText;
255 TraceOut(TraceFlag, message);
258 public static void TraceOut(Exception ex, string Message)
260 var buf = ExceptionOutMessage(ex);
261 TraceOut(TraceFlag, Message + Environment.NewLine + buf);
264 public static void TraceOut(string Message)
266 TraceOut(TraceFlag, Message);
269 public static void TraceOut(bool OutputFlag, string Message)
273 if (!OutputFlag) return;
275 var logPath = MyCommon.GetErrorLogPath();
276 if (!Directory.Exists(logPath))
277 Directory.CreateDirectory(logPath);
279 var now = DateTimeUtc.Now;
280 var fileName = $"{GetAssemblyName()}Trace-{now.ToLocalTime():yyyyMMdd-HHmmss}.log";
281 fileName = Path.Combine(logPath, fileName);
283 using (var writer = new StreamWriter(fileName))
285 writer.WriteLine("**** TraceOut: {0} ****", now.ToLocalTimeString());
286 writer.WriteLine(Properties.Resources.TraceOutText1, ApplicationSettings.FeedbackEmailAddress);
287 writer.WriteLine(Properties.Resources.TraceOutText2, ApplicationSettings.FeedbackTwitterName);
289 writer.WriteLine(Properties.Resources.TraceOutText3);
290 writer.WriteLine(Properties.Resources.TraceOutText4, Environment.OSVersion.VersionString);
291 writer.WriteLine(Properties.Resources.TraceOutText5, Environment.Version);
292 writer.WriteLine(Properties.Resources.TraceOutText6, MyCommon.GetAssemblyName(), FileVersion);
293 writer.WriteLine(Message);
300 // 注意:最終的にファイル出力されるエラーログに記録されるため次の情報は書き出さない
302 // Dataプロパティにある終了許可フラグのパースもここで行う
304 public static string ExceptionOutMessage(Exception ex)
306 bool IsTerminatePermission = true;
307 return ExceptionOutMessage(ex, ref IsTerminatePermission);
310 public static string ExceptionOutMessage(Exception ex, ref bool IsTerminatePermission)
312 if (ex == null) return "";
314 var buf = new StringBuilder();
316 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, ex.GetType().FullName, ex.Message);
320 var needHeader = true;
321 foreach (DictionaryEntry dt in ex.Data)
326 buf.AppendLine("-------Extra Information-------");
329 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
331 if (dt.Key.Equals("IsTerminatePermission"))
333 IsTerminatePermission = (bool)dt.Value;
338 buf.AppendLine("-----End Extra Information-----");
341 buf.AppendLine(ex.StackTrace);
344 //InnerExceptionが存在する場合書き出す
345 var _ex = ex.InnerException;
349 buf.AppendFormat("-----InnerException[{0}]-----\r\n", nesting);
351 buf.AppendFormat(Properties.Resources.UnhandledExceptionText8, _ex.GetType().FullName, _ex.Message);
353 if (_ex.Data != null)
355 var needHeader = true;
357 foreach (DictionaryEntry dt in _ex.Data)
362 buf.AppendLine("-------Extra Information-------");
365 buf.AppendFormat("{0} : {1}", dt.Key, dt.Value);
366 if (dt.Key.Equals("IsTerminatePermission"))
368 IsTerminatePermission = (bool)dt.Value;
373 buf.AppendLine("-----End Extra Information-----");
376 buf.AppendLine(_ex.StackTrace);
379 _ex = _ex.InnerException;
381 return buf.ToString();
384 public static bool ExceptionOut(Exception ex)
388 var IsTerminatePermission = true;
390 var ident = WindowsIdentity.GetCurrent();
391 var princ = new WindowsPrincipal(ident);
392 var now = DateTimeUtc.Now;
394 var errorReport = string.Join(Environment.NewLine,
395 string.Format(Properties.Resources.UnhandledExceptionText1, now.ToLocalTimeString()),
398 string.Format(Properties.Resources.UnhandledExceptionText11 + princ.IsInRole(WindowsBuiltInRole.Administrator)),
399 string.Format(Properties.Resources.UnhandledExceptionText12 + princ.IsInRole(WindowsBuiltInRole.User)),
402 // OSVersion,AppVersion書き出し
403 string.Format(Properties.Resources.UnhandledExceptionText4),
404 string.Format(Properties.Resources.UnhandledExceptionText5, Environment.OSVersion.VersionString),
405 string.Format(Properties.Resources.UnhandledExceptionText6, Environment.Version),
406 string.Format(Properties.Resources.UnhandledExceptionText7, MyCommon.GetAssemblyName(), FileVersion),
408 ExceptionOutMessage(ex, ref IsTerminatePermission));
410 var logPath = MyCommon.GetErrorLogPath();
411 if (!Directory.Exists(logPath))
412 Directory.CreateDirectory(logPath);
414 var fileName = $"{GetAssemblyName()}-{now.ToLocalTime():yyyyMMdd-HHmmss}.log";
415 using (var writer = new StreamWriter(Path.Combine(logPath, fileName)))
417 writer.Write(errorReport);
420 var settings = SettingManager.Common;
421 var mainForm = Application.OpenForms.OfType<TweenMain>().FirstOrDefault();
424 if (mainForm != null && !mainForm.IsDisposed)
425 report = new ErrorReport(mainForm.TwitterInstance, errorReport);
427 report = new ErrorReport(errorReport);
429 report.AnonymousReport = settings.ErrorReportAnonymous;
431 OpenErrorReportDialog(mainForm, report);
433 // ダイアログ内で設定が変更されていれば保存する
434 if (settings.ErrorReportAnonymous != report.AnonymousReport)
436 settings.ErrorReportAnonymous = report.AnonymousReport;
444 private static void OpenErrorReportDialog(Form owner, ErrorReport report)
446 if (owner != null && owner.InvokeRequired)
448 owner.Invoke((Action)(() => OpenErrorReportDialog(owner, report)));
452 using (var dialog = new SendErrorReportForm())
454 dialog.ErrorReport = report;
455 dialog.ShowDialog(owner);
460 /// URLに含まれているマルチバイト文字列を%xx形式でエンコードします。
462 /// マルチバイト文字のコードはUTF-8またはUnicodeで自動的に判断します。
465 /// <param name="_input">エンコード対象のURL</param>
466 /// <returns>マルチバイト文字の部分をUTF-8/%xx形式でエンコードした文字列を返します。</returns>
468 public static string urlEncodeMultibyteChar(string _input)
471 var sb = new StringBuilder(256);
474 foreach (var c in _input)
477 if (Convert.ToInt32(c) > 127 || c == '%') break;
479 if (Convert.ToInt32(c_) <= 127 && c_ != '%') return _input;
481 var input = Uri.UnescapeDataString(_input);
483 foreach (char c in input)
485 if (Convert.ToInt32(c) > 255)
487 // Unicodeの場合(1charが複数のバイトで構成されている)
488 // Uriクラスをnewして再構成し、入力をPathAndQueryのみとしてやり直す
489 foreach (var b in Encoding.UTF8.GetBytes(c.ToString()))
491 sb.AppendFormat("%{0:X2}", b);
494 else if (Convert.ToInt32(c) > 127 || c == '%')
497 // Uriクラスをnewして再構成し、入力をinputからAuthority部分を除去してやり直す
500 uri = new Uri(input);
501 input = input.Remove(0, uri.GetLeftPart(UriPartial.Authority).Length);
507 sb.Append("%" + Convert.ToInt16(c).ToString("X2").ToUpperInvariant());
518 result = sb.ToString();
522 result = uri.GetLeftPart(UriPartial.Authority) + sb;
529 /// URLのドメイン名をPunycode展開します。
531 /// ドメイン名がIDNでない場合はそのまま返します。
532 /// ドメインラベルの区切り文字はFULLSTOP(.、U002E)に置き換えられます。
535 /// <param name="inputUrl">展開対象のURL</param>
536 /// <returns>IDNが含まれていた場合はPunycodeに展開したURLをを返します。Punycode展開時にエラーが発生した場合はnullを返します。</returns>
537 public static string IDNEncode(string inputUrl)
541 var uriBuilder = new UriBuilder(inputUrl);
543 var idnConverter = new IdnMapping();
544 uriBuilder.Host = idnConverter.GetAscii(uriBuilder.Host);
546 return uriBuilder.Uri.AbsoluteUri;
554 public static string IDNDecode(string inputUrl)
558 var uriBuilder = new UriBuilder(inputUrl);
560 if (uriBuilder.Host != null)
562 var idnConverter = new IdnMapping();
563 uriBuilder.Host = idnConverter.GetUnicode(uriBuilder.Host);
566 return uriBuilder.Uri.AbsoluteUri;
575 /// URL を画面上で人間に読みやすい文字列に変換する(エスケープ解除など)
577 public static string ConvertToReadableUrl(string inputUrl)
581 var outputUrl = inputUrl;
584 outputUrl = MyCommon.IDNDecode(outputUrl);
585 if (outputUrl == null)
588 // URL内で特殊な意味を持つ記号は元の文字に変換されることを避けるために二重エスケープする
589 // 参考: Firefoxの losslessDecodeURI() 関数
590 // http://hg.mozilla.org/mozilla-central/annotate/FIREFOX_AURORA_27_BASE/browser/base/content/browser.js#l2128
591 outputUrl = Regex.Replace(outputUrl, @"%(2[3456BCF]|3[ABDF]|40)", @"%25$1", RegexOptions.IgnoreCase);
594 outputUrl = Uri.UnescapeDataString(outputUrl);
598 catch (UriFormatException)
604 public static void MoveArrayItem(int[] values, int idx_fr, int idx_to)
606 var moved_value = values[idx_fr];
607 var num_moved = Math.Abs(idx_fr - idx_to);
611 Array.Copy(values, idx_to, values,
612 idx_to + 1, num_moved);
616 Array.Copy(values, idx_fr + 1, values,
620 values[idx_to] = moved_value;
623 public static string EncryptString(string str)
625 if (string.IsNullOrEmpty(str)) return "";
628 var bytesIn = Encoding.UTF8.GetBytes(str);
630 //DESCryptoServiceProviderオブジェクトの作成
631 using (var des = new DESCryptoServiceProvider())
635 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
637 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
638 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
640 MemoryStream msOut = null;
641 ICryptoTransform desdecrypt = null;
645 //暗号化されたデータを書き出すためのMemoryStream
646 msOut = new MemoryStream();
649 desdecrypt = des.CreateEncryptor();
651 //書き込むためのCryptoStreamの作成
652 using (CryptoStream cryptStream = new CryptoStream(msOut, desdecrypt, CryptoStreamMode.Write))
654 //Disposeが重複して呼ばれないようにする
655 MemoryStream msTmp = msOut;
660 cryptStream.Write(bytesIn, 0, bytesIn.Length);
661 cryptStream.FlushFinalBlock();
663 var bytesOut = msTmp.ToArray();
665 //Base64で文字列に変更して結果を返す
666 return Convert.ToBase64String(bytesOut);
672 desdecrypt?.Dispose();
677 public static string DecryptString(string str)
679 if (string.IsNullOrEmpty(str)) return "";
681 //DESCryptoServiceProviderオブジェクトの作成
682 using (var des = new System.Security.Cryptography.DESCryptoServiceProvider())
686 var bytesKey = Encoding.UTF8.GetBytes("_tween_encrypt_key_");
688 des.Key = ResizeBytesArray(bytesKey, des.Key.Length);
689 des.IV = ResizeBytesArray(bytesKey, des.IV.Length);
691 //Base64で文字列をバイト配列に戻す
692 var bytesIn = Convert.FromBase64String(str);
694 MemoryStream msIn = null;
695 ICryptoTransform desdecrypt = null;
696 CryptoStream cryptStreem = null;
700 //暗号化されたデータを読み込むためのMemoryStream
701 msIn = new MemoryStream(bytesIn);
703 desdecrypt = des.CreateDecryptor();
704 //読み込むためのCryptoStreamの作成
705 cryptStreem = new CryptoStream(msIn, desdecrypt, CryptoStreamMode.Read);
707 //Disposeが重複して呼ばれないようにする
711 //復号化されたデータを取得するためのStreamReader
712 using (StreamReader srOut = new StreamReader(cryptStreem, Encoding.UTF8))
714 //Disposeが重複して呼ばれないようにする
718 var result = srOut.ReadToEnd();
726 desdecrypt?.Dispose();
727 cryptStreem?.Dispose();
732 public static byte[] ResizeBytesArray(byte[] bytes,
735 var newBytes = new byte[newSize];
736 if (bytes.Length <= newSize)
738 foreach (var i in Enumerable.Range(0, bytes.Length))
740 newBytes[i] = bytes[i];
746 foreach (var i in Enumerable.Range(0, bytes.Length))
748 newBytes[pos] = unchecked((byte)(newBytes[pos] ^ bytes[i]));
750 if (pos >= newBytes.Length)
760 public enum TabUsageType
764 Mentions = 2, //Unique
765 DirectMessage = 4, //Unique
766 Favorites = 8, //Unique
768 LocalQuery = 32, //Pin(no save/no save query/distribute/no update(normal update))
769 Profile = 64, //Pin(save/no distribute/manual update)
770 PublicSearch = 128, //Pin(save/no distribute/auto update)
775 SearchResults = 4096,
781 public static TwitterApiStatus TwitterApiInfo = new TwitterApiStatus();
783 public static bool IsAnimatedGif(string filename)
788 img = Image.FromFile(filename);
789 if (img == null) return false;
790 if (img.RawFormat.Guid == ImageFormat.Gif.Guid)
792 var fd = new FrameDimension(img.FrameDimensionsList[0]);
793 var fd_count = img.GetFrameCount(fd);
815 public static DateTimeUtc DateTimeParse(string input)
817 var formats = new[] {
818 "ddd MMM dd HH:mm:ss zzzz yyyy",
819 "ddd, d MMM yyyy HH:mm:ss zzzz",
822 if (DateTimeUtc.TryParseExact(input, formats, DateTimeFormatInfo.InvariantInfo, out var result))
825 TraceOut("Parse Error(DateTimeFormat) : " + input);
827 return DateTimeUtc.Now;
830 public static T CreateDataFromJson<T>(string content)
833 var buf = Encoding.Unicode.GetBytes(content);
834 using (var stream = new MemoryStream(buf))
836 var settings = new DataContractJsonSerializerSettings
838 UseSimpleDictionaryFormat = true,
840 data = (T)((new DataContractJsonSerializer(typeof(T), settings)).ReadObject(stream));
845 public static bool IsNetworkAvailable()
849 return NetworkInterface.GetIsNetworkAvailable();
857 public static bool IsValidEmail(string strIn)
859 // Return true if strIn is in valid e-mail format.
860 return Regex.IsMatch(strIn,
861 @"^(?("")("".+?""@)|(([0-9a-zA-Z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-zA-Z])@))" +
862 @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,6}))$");
866 /// 指定された修飾キーが押されている状態かを取得します。
868 /// <param name="keys">状態を調べるキー</param>
869 /// <returns><paramref name="keys"/> で指定された修飾キーがすべて押されている状態であれば true。それ以外であれば false。</returns>
870 public static bool IsKeyDown(params Keys[] keys)
872 return MyCommon._IsKeyDown(Control.ModifierKeys, keys);
875 internal static bool _IsKeyDown(Keys modifierKeys, Keys[] targetKeys)
877 foreach (Keys key in targetKeys)
879 if ((modifierKeys & key) != key)
888 /// アプリケーションのアセンブリ名を取得します。
891 /// VB.NETの<code>My.Application.Info.AssemblyName</code>と(ほぼ)同じ動作をします。
893 /// <returns>アプリケーションのアセンブリ名</returns>
894 public static string GetAssemblyName()
896 return MyCommon.EntryAssembly.GetName().Name;
900 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
902 /// <param name="orig">対象となる文字列</param>
903 /// <returns>置換後の文字列</returns>
904 public static string ReplaceAppName(string orig)
906 return MyCommon.ReplaceAppName(orig, ApplicationSettings.ApplicationName);
910 /// 文字列中に含まれる %AppName% をアプリケーション名に置換する
912 /// <param name="orig">対象となる文字列</param>
913 /// <param name="appname">アプリケーション名</param>
914 /// <returns>置換後の文字列</returns>
915 public static string ReplaceAppName(string orig, string appname)
917 return orig.Replace("%AppName%", appname);
921 /// 表示用のバージョン番号の文字列を生成する
924 /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-beta1」が出力される
929 public static string GetReadableVersion(string versionStr = null)
931 var version = Version.Parse(versionStr ?? MyCommon.FileVersion);
933 return GetReadableVersion(version);
937 /// 表示用のバージョン番号の文字列を生成する
940 /// バージョン1.0.0.1のように末尾が0でない(=開発版)の場合は「1.0.1-dev」のように出力される
945 public static string GetReadableVersion(Version version)
947 var versionNum = new[] { version.Major, version.Minor, version.Build, version.Revision };
949 if (versionNum[3] == 0)
951 return string.Format("{0}.{1}.{2}", versionNum[0], versionNum[1], versionNum[2]);
955 versionNum[2] = versionNum[2] + 1;
958 if (versionNum[2] >= 10)
960 versionNum[1] += versionNum[2] / 10;
963 if (versionNum[1] >= 10)
965 versionNum[0] += versionNum[1] / 10;
970 if (versionNum[3] == 1)
971 return string.Format("{0}.{1}.{2}-dev", versionNum[0], versionNum[1], versionNum[2]);
973 return string.Format("{0}.{1}.{2}-dev (Build {3})", versionNum[0], versionNum[1], versionNum[2], versionNum[3]);
977 public const string TwitterUrl = "https://twitter.com/";
979 public static string GetStatusUrl(PostClass post)
981 if (post.RetweetedId == null)
982 return GetStatusUrl(post.ScreenName, post.StatusId);
984 return GetStatusUrl(post.ScreenName, post.RetweetedId.Value);
987 public static string GetStatusUrl(string screenName, long statusId)
989 return TwitterUrl + screenName + "/status/" + statusId;
993 /// 指定された IDictionary を元にクエリ文字列を生成します
995 /// <param name="param">生成するクエリの key-value コレクション</param>
996 public static string BuildQueryString(IEnumerable<KeyValuePair<string, string>> param)
1002 .Where(x => x.Value != null)
1003 .Select(x => EscapeQueryString(x.Key) + '=' + EscapeQueryString(x.Value));
1005 return string.Join("&", query);
1008 // .NET 4.5+: Reserved characters のうち、Uriクラスによってエスケープ強制解除されてしまうものも最初から Unreserved として扱う
1009 private static readonly HashSet<char> UnreservedChars =
1010 new HashSet<char>("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~!'()*:");
1013 /// 2バイト文字も考慮したクエリ用エンコード
1015 /// <param name="stringToEncode">エンコードする文字列</param>
1016 /// <returns>エンコード結果文字列</returns>
1017 public static string EscapeQueryString(string stringToEncode)
1019 var sb = new StringBuilder(stringToEncode.Length * 2);
1021 foreach (var b in Encoding.UTF8.GetBytes(stringToEncode))
1023 if (UnreservedChars.Contains((char)b))
1026 sb.AppendFormat("%{0:X2}", b);
1029 return sb.ToString();
1033 /// 指定された範囲の整数を昇順に列挙します
1036 /// start, start + 1, start + 2, ..., end の範囲の数列を生成します
1038 /// <param name="from">数列の先頭の値 (最小値)</param>
1039 /// <param name="to">数列の末尾の値 (最大値)</param>
1040 /// <returns>整数を列挙する IEnumerable インスタンス</returns>
1041 public static IEnumerable<int> CountUp(int from, int to)
1044 return Enumerable.Empty<int>();
1046 return Enumerable.Range(from, to - from + 1);
1050 /// 指定された範囲の整数を降順に列挙します
1053 /// start, start - 1, start - 2, ..., end の範囲の数列を生成します
1055 /// <param name="from">数列の先頭の値 (最大値)</param>
1056 /// <param name="to">数列の末尾の値 (最小値)</param>
1057 /// <returns>整数を列挙する IEnumerable インスタンス</returns>
1058 public static IEnumerable<int> CountDown(int from, int to)
1060 for (var i = from; i >= to; i--)
1064 public static IEnumerable<int> CircularCountUp(int length, int startIndex)
1067 throw new ArgumentOutOfRangeException(nameof(length));
1068 if (startIndex < 0 || startIndex >= length)
1069 throw new ArgumentOutOfRangeException(nameof(startIndex));
1071 // startindex ... 末尾
1072 var indices = MyCommon.CountUp(startIndex, length - 1);
1074 // 先頭 ... (startIndex - 1)
1075 if (startIndex != 0)
1076 indices = indices.Concat(MyCommon.CountUp(0, startIndex - 1));
1081 public static IEnumerable<int> CircularCountDown(int length, int startIndex)
1084 throw new ArgumentOutOfRangeException(nameof(length));
1085 if (startIndex < 0 || startIndex >= length)
1086 throw new ArgumentOutOfRangeException(nameof(startIndex));
1088 // startIndex ... 先頭
1089 var indices = MyCommon.CountDown(startIndex, 0);
1091 // 末尾 ... (startIndex + 1)
1092 if (startIndex != length - 1)
1093 indices = indices.Concat(MyCommon.CountDown(length - 1, startIndex + 1));
1099 /// 2バイト文字も考慮したUrlエンコード
1101 /// <param name="stringToEncode">エンコードする文字列</param>
1102 /// <returns>エンコード結果文字列</returns>
1103 public static string UrlEncode(string stringToEncode)
1105 const string UnreservedChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
1106 StringBuilder sb = new StringBuilder();
1107 byte[] bytes = Encoding.UTF8.GetBytes(stringToEncode);
1109 foreach (byte b in bytes)
1111 if (UnreservedChars.IndexOf((char)b) != -1)
1114 sb.AppendFormat("%{0:X2}", b);
1116 return sb.ToString();