رفع باگ موجود در CookieContainer

16 10 2009

قبلا نوشته بودم که ماکروسافت قراره باگی رو که در کلاس CookieContainer هست رو رفع کنه، که اون باگ باعث 7 ماه سرگردانی من شده است. خبر خوبی بود، اما اگر به سایت ماکروسافت که لینکش رو گذاشته بودم مراجعه کنید می بینید که هیچ وصله ای قرار نیست برای دات نت 2 منتشر بشه. و این یعنی اینکه همچنان با مشکل باید دست و پنجه نرم کنیم!

به هر حال در ادامه می خوام علت بروز مشکل و راه حل اون رو با زحمت بسیار و همکاری یه نفر دیگه بدست اومده رو بررسی کنم.

  • مقدمه ای در مورد Domain در کوکی ها

تمام مشکل حول محور نحوه مدیریت domain در کلاس Cookiecontainer بر میگرده. پس میخوام مختصر توضیحی در مورد دامین در کوکی ها بدم.

در مبحث کار با کوکی ها با تنظیم مقدار domain می توانید تعیین کنید که کوکی برای کدام دامنه یا زیر دامنه ها قابل دسترسی باشد.

یک مثال ساده مانند زیر هست؛ در این مثال  کوکی با نام Hello رو برای دامین site.org ثبت می کنیم:

Hello=cookieValue value; domain=site.org; path=/

توجه کنید که این کوکی فقط به دامین site.org ثبت شده و فقط برای آن قابل مشاهده خواهد بود. هیچ زیر دامنه ای مانند http://www.site.org نمی تواند این کوکی رو مشاهده کند. برای اینکه کوکی به زیر دامنه های سایت site.org هم قابل دسترسی باشه باید یک نقطه (.) به اول دامنه اضافه کنیم. مانند این نمونه:

Hello=cookieValue value; domain=.site.org; path=/

با این کار این کوکی به تمامی زیر دامنه ها قابل مشاهده خواهد بود.

همچنین توجه کنید که این قانون به کوکی های خود زیر دامنه ها هم اعمال میشه. برای مثال کوکی زیر فقط برای sub.site.org قابل مشاهده خواهد بود:

Hello=cookieValue value; domain=sub.site.org; path=/

و کوکی زیر، هم برای دامنه اصلی و هم برای تمامی زیر دامنه های آن قابل مشاهده خواهد بود:

Hello=cookieValue value; domain=.sub.site.org; path=/

  • بررسی مشکل

قبل از اینکه به مشکل بپردازم لازمه بدونید که کلاس CookieContainer برای مدیریت و نگهداری کوکی های برای ارسال درخواست های وب توسط HttpWebRequest مورد استفاده قرار میگیره. و به علت مشکلی که داره بعضا باعث اشکالات عجیب و نامشخص می شه.

احتمالا تا حالا متوجه شدید که مشکل کلاس CookieContainer در نحوه مدیریت نقطه (.) هایی هست که در ابتدای دامین قرار می گیره و به شیوه اعمال اونها تاثیر میذاره.

برای نمونه مشکل زمانی اتفاق می افته که یک کوکی رو به آدرس site.org ثبت کنید و درخواست خودتون رو آدرس http://www.site.org ارسال کنید.
طبق مقدمه انتظار میره که هیچ کوکی ارسال نشه ولی برخلاف انتطار کوکی مزبور ارسال خواهد شد. متن کوکی این مثال:

Hello=CookieValue value; domain=site.org; path=/

همچینین مشکل دیگر و البته بدتر زمانی هست که کوکی رو به آدرس http://www.site.org ثبت کنید و درخواست خودتون رو به آدرس http://www.site.org ارسال کنید. انتظار می ره که کوکی به سلامتی ارسال بشه ولی در کمال ناباوری هیچ کوکی ارسال نخواهد شد. متن کوکی این مثال:

Hello=CookieValue value; domain=www.site.org; path=/

علت بروز مشکل به یک سری پیچیدگیهای اضافی که ماکروسافت در پیاده سازی این کلاس اعمال کرده بر می گرده.

  • نحوه رفع باگ

مشکل اصلی به نحوه پیاده سازی یکی از متد ها داخلی و پنهان کلاس CookieContainer بر می گرده. در نتییجه توابعی که از اون تابع استفاده می کنند این مشکل زو خواهند داشت. متاسفانه همه سه تابعی که برای افزودن کوکی قابل دسترسی هستند از مشکل رنج می برند! این سه تابع و توضیح مختصر در مورد اونها:

  • Add(Uri,Cookie) این تابع کوکی را اضافه کرده و به مسیر تعیین شده ارتباط میدهد
  • Add(Cookie) این تابع کوکی رو اضافه کرده و دامینهای قابل اعمال رو از دامین کوکی تعیین می کنه
  • SetCookies(CookieHeader) این تابع کوکی که به صورت هدر کوکی ارسال شده رو تولید کرده و اضافه می کنه.

راه حل قطعی که تا کنون با همکاری دوستم تونستیم پیدا کنیم تغییر کلید های دسترسی به کوکی و اضافه کردن کلید های مفقود شده به صورت دستی با استفاده از امکانات Reflection دات نت.

به صورت خلاصه تابع زیر برای رفع این نقیصه نوشته شده. این تابع رو همراه با یک سری تمهیدات باید به کار ببرد که در ادامه توضیح خواهد داد:

private static Type _cookieContainerType = Type.GetType("System.Net.CookieContainer, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
private static Type _pathListType = Type.GetType("System.Net.PathList, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
///<summary>
/// This method is aimed to fix a goddamn CookieContainer issue,
/// It adds missed path for cookies which are not started with dot.
/// This is a dirty hack
///</summary>
///<remarks>
/// This method is only for .NET 2.0 which is used by .NET 3.0 and 3.5 too.
/// The issue will be fixed in .NET 4, I hope!
///</remarks>
/// Many thanks to CallMeLaNN "dot-net-expertise.blogspot.com" to complete this method
private void BugFix_AddDotCookieDomain(CookieContainer cookieContainer)
{
	Hashtable table = (Hashtable)_cookieContainerType.InvokeMember("m_domainTable",
									 System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance,
									 null,
									 cookieContainer,
									 new object[] { });

	ArrayList keys = new ArrayList(table.Keys);

	object pathList1;
	object pathList2;

	SortedList sortedList1;
	SortedList sortedList2;
	ArrayList pathKeys;

	CookieCollection cookieColl1;
	CookieCollection cookieColl2;

	foreach (string key in keys)
	{
		if (key[0] == '.')
		{
			string nonDotKey = key.Remove(0, 1);
			// Dont simply code like this:
			// table[nonDotKey] = table[key];
			// instead code like below:

			// This codes will copy all cookies in dot domain key into nondot domain key.

			pathList1 = table[key];
			pathList2 = table[nonDotKey];
			if (pathList2 == null)
			{
				pathList2 = Activator.CreateInstance(_pathListType); // Same as PathList pathList = new PathList();
				lock (cookieContainer)
				{
					table[nonDotKey] = pathList2;
				}
			}

			// merge the PathList, take cookies from table[keyObj] copy into table[nonDotKey]
			sortedList1 = (SortedList)_pathListType.InvokeMember("m_list", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, pathList1, new object[] { });
			sortedList2 = (SortedList)_pathListType.InvokeMember("m_list", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.GetField | System.Reflection.BindingFlags.Instance, null, pathList2, new object[] { });

			pathKeys = new ArrayList(sortedList1.Keys);

			foreach (string pathKey in pathKeys)
			{

				cookieColl1 = (CookieCollection)sortedList1[pathKey];
				cookieColl2 = (CookieCollection)sortedList2[pathKey];
				if (cookieColl2 == null)
				{
					cookieColl2 = new CookieCollection();
					sortedList2[pathKey] = cookieColl2;
				}

				foreach (Cookie c in cookieColl1)
				{
					lock (cookieColl2)
					{
						cookieColl2.Add(c);
					}
				}
			}
		}
	}
}

جزئیات نحوه کار این متد زیاد مهم نخواهد بود پس از ذکر آنها خودداری می کنم.

شیوه به کار گیری:

نکته خیلی خیلی مهم این هست که از هیچ کدام از توابع Add(Uri,Cookie)  و SetCookies(CookieHeader) نباید استفاده کنید. مشکلاتی این دو تابع ایجاد می کنند غیر قابل اصلاح است.

تنها تابع مجاز برای استفاده Add(Cookie) جها اضافه کردن کوکی های به کلاس CookieContainer است.

و سر انجام اینکه تابع BugFix_AddDotCookieDomain رو زمانی باید فراخوانی کنید که کارتان با کلاس CookieContainer به پایان رسیده و آماده ارسال درخواست هستید.

این تابع تمام مشکلات ذکر شده در بالا را رفع خواهد کرد.

نکته پایانی

تنها موردی که باقی ماند که نیاز اساسی به یه راه حل داره تابع SetCookies هست. چون این تابع با هدر ها کار می کنه ممکنه که به اون نیاز پیدا کنید. متاسفانه همونطور که گفتم مشکل مربوط به SetCookies هنوز رفع نشده و من و دوستم به دنبال راه حلی برای این مورد هستیم.

  • لینکهای مرتبط

بحث طولانی من و دوستم که منجر به رسیدن به این راه حل شد

وصله ماکروسافت برای رفع مشکل 7 ماهه من

پاسخ ماکروسافت به این مشکل


کارها

Information

پاسخی بگذارید

در پایین مشخصات خود را پر کنید یا برای ورود روی شمایل‌ها کلیک نمایید:

نشان‌وارهٔ وردپرس.کام

شما در حال بیان دیدگاه با حساب کاربری WordPress.com خود هستید. بیرون رفتن / تغییر دادن )

تصویر توییتر

شما در حال بیان دیدگاه با حساب کاربری Twitter خود هستید. بیرون رفتن / تغییر دادن )

عکس فیسبوک

شما در حال بیان دیدگاه با حساب کاربری Facebook خود هستید. بیرون رفتن / تغییر دادن )

عکس گوگل+

شما در حال بیان دیدگاه با حساب کاربری Google+ خود هستید. بیرون رفتن / تغییر دادن )

درحال اتصال به %s




%d وب‌نوشت‌نویس این را دوست دارند: