برنامج Website Blocker في الدوت نت

النافذة الرئيسة لبرنامج WebSiteBlocker VB.NET & C#

 
سنقوم في هذا الدرس بإنشاء برنامج بسيط لحجب المواقع, حيث يمكننا من حجب المواقع التي نريدها وبذلك يمكنك التحكم في المواقع التي غير مسموح بأن يتم تصفحها على جهازك.
    

فكرة البرنامج:                                                                                   

في نظام التشغيل Windows يوجد ملف يسمى "hosts" (بدون لاحقة) يوجد في المسار التالي:
C:\Windows\System32\drivers\etc\

هذا الملف هو عبارة عن ملف نصي عادي اي يمكنك فتحه بالمفكرة, يحوي عناوين المواقع المضيفة, ستجد بداخله شرح باللغة الإنكليزية لكيفية حجب موقع ما, حيث انك لتقوم بحجب موقع ما يجب عليك كتابة التالي في سطر جديد:

آولاً رقم الآيبي IP الذي تريد حجب الموقع عنه حيث يمكنك حجب الموقع عن إتصال محدد في حال كان هذا الإتصال يملك آيبي ثابت اي غير متغير Static IP او يمكنك حجب الموقع بالكامل عن جميع الاتصالات بكتابة رقم آيبي محدد سأخبرك به لاحقاً.

ثانياً نقوم بترك فراغ (مسافة) واحدة على الأقل ثم نكتب بعد الفراغ رابط الموقع الذي نريد حجبه بشكل مجرد أي بدون http://.
مثال لحجب موقع جوجل عن آيبي محدد مثل 192.168.20.1:
192.168.20.1 www.google.com
أما هذا المثال التالي فسيقوم بحجب الموقع بشكل كامل عن الجهاز:
127.0.0.1 www.google.com

حيث ان العنوان (127.0.0.1) نستخدمه لحجب الموقع بشكل كامل لأن هذا العنوان هو عنوان الكمبيوتر عندما لا يكون متصل بأي شبكة أي عنوان المضيف المحلي Local Host, طبعاً هذا العنوان هو عنوان المضيف لعناوين IP4 اما في حالة كنت تريد العنوان المضيف لعناوين IP6 فيجب كتابة عنوان المضيف الخاص به والذي هو (1:: ).

الملف يحوي اسطر آخرى وهي تكون عبارة عن تعليق اي ليس لها علاقة بعملية الحجب ولجعل السطر عبارة عن تعليق يجب وضع علامة # في بداية السطر.

كتابة البرنامج:                                                                                  

يتألف البرنامج من فورم يحوي قائمة بالمواقع المحجوبة مع بعض الأزرار لإضافة وحذف موقع من الملف, وفورم آخر لإضافة رابط إلى الملف.

لفورم الرئيسية:                                                                                

أولاً نقوم بتعريف المتغييرين التاليين على مستوى الفورم:
DataTable dtSites;
string hostPath;
Private dtSites As DataTable
Private hostPath As String

المتغير الأول عبارة عن جدول سنقوم بوضع البيانات الموجودة في الملف من أجل تسهيل التعامل معها لاحقاً لأن التعامل مع 
الملفات النصية ليس مرناً كثيراً.
أما المتغير الثاني فسيحوي مسار ملف حجب المواقع hosts.
   
شيفرة الحدث Form_Load:                                                                
PrepareDT();

hostPath = Environment.GetFolderPath(Environment.SpecialFolder.System);
hostPath += @"\drivers\etc\hosts";

btnRefresh.PerformClick();
PrepareDT()
                                  
hostPath = Environment.GetFolderPath(Environment.SpecialFolder.System)
hostPath &= "\drivers\etc\hosts"
                         
btnRefresh.PerformClick()

السطرالأول يستدعي الإجراء PrepareDT  الذي يقوم بتجهيز الجدول الذي عرفناه في البداية  dtSites.

السطرين التاليين يقومان بالحصول على مسار الملف حيث في البداية نحصل على مسار المجلد System32 ثم نقوم بإضافة باقي المسار يدوياً.

ثم نقوم بالضغط على زر التحديث من أجل تنفيذ الحدث التابع له والذي فيه يتم معالجة الملف hosts وجلب البيانات منه ووضعها في الجدول.

الإجراء PrepareDT:                                                                                                             

أولاً نقوم بإنشاء نسخة جديدة من كائن الجدول dtSites ثم تعريف متغير نصي لا تهمنا قيمته لأننا سنستخدمه فقط من أجل إنشاء الأعمدة في الجدول.
dtSites = new DataTable();
string tmp = "";
dtSites = New DataTable()
Dim tmp As String = ""

ثانياً إنشاء عمودين هما:
  • ip: وهذا العمود قيمته نصية وسيحوي قيمة الآيبي لكل سطر في الملف.
  • url: أما هذا العمود فسيحوي رابط الموقع الذي تم حجبه وأيضاً قيمته نصية.
DataColumn ip = new DataColumn("ip", tmp.GetType());
DataColumn url = new DataColumn("url", tmp.GetType());

dtSites.Columns.Add(ip);
dtSites.Columns.Add(url);
Dim ip As New DataColumn("ip", tmp.[GetType]())
Dim url As New DataColumn("url", tmp.[GetType]())
                      
dtSites.Columns.Add(ip)
dtSites.Columns.Add(url)

كما يبدو لك قمنا بتعريف المتغير النصي tmp فقط لتحديد نوع بيانات العمودين السابقين على انه نصي.
آخيراً إضافة العمودين إلى الجدول وبهذا يصبح الجدول جاهز لتخزين البيانات فيه.
     
كتابة الكود لحدث الضغط على زر تحديث:                                                    

الزر تحديث يقوم بقراءة محتوايات الملف Hosts ووضع النتائج في الكائن dtSites الذي سنستخدمه بعد ذلك لعرض النتائج:
أولاً نقوم بإنشاء الكائن من نوع StreamReader لقراءة الملف طبعاً ثم نقوم بتغريغ الجدول من البيانات اذا كان يحوي بيانات سابقة.
اذا كنت لا تعرف استخدم الكلاس StreamReader , فأنصحك بالإطلاع على الدرس برنامج المفكرة.

StreamReader sr = new StreamReader(hostPath);
dtSites.Rows.Clear();
Dim sr As New StreamReader(hostPath)
dtSites.Rows.Clear()

لقراءة الملف النصية يجب عمل حلقة للمرور على جميع الأسطر وشرط الحلقة هو طالما لم يتم الوصول إلى نهاية الملف تابع القراءة.

while (sr.EndOfStream == false)
{
    string curLine = sr.ReadLine().Trim();

    if (curLine.StartsWith("#") || curLine.Length == 0)
    {
        DataRow dr = dtSites.Rows.Add();
        dr["ip"] = curLine;
    }
    else
    {
        string[] data = curLine.Split(' ');

        if (data.Length == 2)
        {
            DataRow dr = dtSites.Rows.Add();
            dr["ip"] = data[0];                                                                      
            dr["url"] = data[1];
        }
        elseif (data.Length > 2)
        {
            DataRow dr = dtSites.Rows.Add();
            dr["ip"] = data[0];
 for (int i = 1; i < dr.Length; i++)
{
                if (data[i].Trim().Length == 0 || data[i].StartsWith(";"))
                    continue;
                else
                {
                    dr["url"] = data(i);
                    breake;
                }
            }           
        }
    }
}
While sr.EndOfStream = False
    Dim curLine As String = sr.ReadLine().Trim()

    If curLine.StartsWith("#") OrElse curLine.Length = 0 Then
        Dim dr As DataRow = dtSites.Rows.Add()
        dr("ip") = curLine
    Else
        Dim data As String() = curLine.Split(" "c)

        If data.Length = 2 Then
            Dim dr As DataRow = dtSites.Rows.Add()
            dr("ip") = data(0)
            dr("url") = data(1)

        ElseIf data.Length > 2 Then
            Dim dr As DataRow = dtSites.Rows.Add()
            dr("ip") = data(0)

            For i As Integer = 1 To (data.Length - 1)
                If data(i).Trim().Length = 0 OrElse data(i).StartsWith(";") Then
                    Continue For
                Else
                    dr("url") = data(i)
                    Exit For
                End If
            Next
        End If
    End If
End While

طبعاً عند تعريف الكائن sr فإن الملف hosts سيتم فتحه ووضع مؤشر القراءة على السطر الأول لذلك نقوم بقراءة سطر في كل دورة من الحلقة ثم وضع قيمة هذا  السطر في المتغير النصي curLine مع تنفيذ التابع Trim لإزالة الفراغات من على طرفي السطر.
 
وصلنا الآن للجزء المهم والذي فيه يتم معالجة السطر ومعرفة هل يحوي بيانات ام هو سطر فارغ ام سطر تعليق ,, لذلك نقوم بعمل شرط للتأكد من ان السطر لا يبدأ بعلامة # أي انه ليس تعليقا و ايضاً ان السطر ليس فارغ, في حال تحقق هذا السطر فإننا نقوم بإضافة قيمة السطر كما هو إلى الجدول وبالتحديد إلى العمود الأول ip وسيتضح سبب هذا الأمر لاحقاً.
 
في حال لم يتحقق الشرط فإننا نقوم بتقسيم السطر بواسطة التابع Split إلى أجزاء عن طريق المسافة (الفراغ) لأننا كما نعلم انه يتم فصل الآيبي عن رابط الموقع بفراغ واحد على الاقل.
 
التابع Split يقوم بإعادة مصفوفة نصية تحوي الأقسام التي نتجت من تنفيذ هذا التابع ,, وهنا سيناقش الكود احتمالين:
  • طول المصوفة الناتجة data() هو 2 اي ان السطر الذي تمت قرائته يحوي الايبي وعنوان الموقع فقط لذلك نقوم بقراءة كل قيمة على حدى ووضعها في المتغير المخصص لها.
  • طول المصفوفة data() اكبر من 2 اي ان السطر يحوي على اكثر من الايبي وعنوان الموقع (على الأغلب يكون تعليق) في هذه الحالة سنعتبر القيمة الأولى في المصفوفة هو الايبي فهذا هو المتوقع, ثم نقوم بعمل حلقة للدوران على باقي قيم المصفوفة وفحصها حتى نجد القيمة التي تحوي عنوان الموقع وعندها نخرج من الحلقة.
بعد الانتهاء من تنفيذ الحلقة ومعالجة الملف نقوم باستدعاء الإجراء showData لعرض البيانات على الفورم.
 
الإجراء showData:                                                                        

سنقوم الآن بشرح الكود الذي سيقوم بعرض النتائج في الليست فيو:
اولاً نقوم بتفريغ القائمة ListView من القيمة السابقة ان وجدت ثم سنقوم بتنفيذ استعلام على الجدول dtSites للحصول على الاسطر التي تحوي بيانات الآيبي والرابط لإضافتها.
lvwSites.Items.Clear();
DataRow[] dr = dtSites.Select("url NOT IS NULL")
lvwSites.Items.Clear()
Dim dr As DataRow() = dtSites.[Select]("url NOT IS NULL")

التابع Select يقوم بتنفيذ استعلام Select على الجدول ونمرر له كبارمتر شرط هذا الاستعلام اي سيكون شكل الاستعلام الذي سينفذ:
Select * From dtSites Where url NOT IS NULL
العبارة التي باللون الازرق ستكون جاهزة ونحن فقط اضفنا الشرط الذي هو باللون الأحمر وبهذا نحصل على الاسطر التي تحوي بيانات الآيبي والرابط وذلك بوضع شرط ان لا يكون حقل الرابط url فارغاً.
وهذا الشرط يتحقق فقط في الاسطر التي تحوي على آيبي ورابط بينما الاسطر الغير مهمة فـقمنا بتخزينها في الحقل ip وتركنا الحقل url فارغاً من أجل هذا الأمر بالتحديد.
for (int i = 0; i < dr.Length; i++)
{
    ListViewItem itm = new ListViewItem();
    itm.Text = dr[i]["url"].ToString();
    itm.SubItems.Add(dr[i]["ip"].ToString());             
    lvwSites.Items.Add(itm);
}
For i As Integer = 0 To (dr.Length - 1)
    Dim itm As New ListViewItem()
    itm.Text = dr(i)("url").ToString()
    itm.SubItems.Add(dr(i)("ip").ToString()) 
    lvwSites.Items.Add(itm)
Next

باقي الكود هو حلقة للدوران على ناتج تنفيذ التابع Select والذي يعيد مصفوفة أسطر DataRow Array الناتجة من تنفيذ الاستعلام Select ثم إضافتها إلى القائمة ListView.
 
كود الزر حذف:                                                                                  

الزر حذف يقوم بحذف العناصر المحددة في الليست فيو عن طريق عمل حلقة للدوران على العناثر المحددة ثم حذفها:
for (int i = 0; i < lvwSites.CheckedItems.Count; i++)
{
    string ip = lvwSites.CheckedItems[i].SubItems[1].Text;
    string url = lvwSites.CheckedItems[i].Text;
           
    DataRow[] dr = dtSites.Select(string.Format("ip='{0}' AND url='{1}'", ip, url));

    for (int j = 0; j < dr.Length; j++)
    {
        dtSites.Rows.Remove(dr[j]);
    }
}
For i As Integer = 0 To (lvwSites.CheckedItems.Count - 1)
    Dim ip As String = lvwSites.CheckedItems(i).SubItems(1).Text
    Dim url As String = lvwSites.CheckedItems(i).Text

    Dim dr As DataRow() = dtSites.[Select](String.Format("ip='{0}' AND url='{1}'", ip, url))

    For j As Integer = 0 To (dr.Length - 1)
        dtSites.Rows.Remove(dr(j))
    Next
Next

داخل الحلقة نقوم بعمل استعلام Select على الجدول للحصول على السطر او الاسطر التي تطابق بياناتها بيانات العنصر المحدد في القائمة, وشرط الحلقة هو ان يتطابق حقلي ip و url مع القيم المأخوذة من العنصر المحدد في القائمة.

Select * From dtSites Where ip = TheIp AND url = TheUrl
 
بعد ذلك نقوم بحذف السطر او الاسطر التي نتجت من إجراء الاستعلام.
 
أخيراً بقي ان نقوم بحفظ البيانات من جديد إلى الملف hosts لذلك نعرف كائن StreamWriter مع تمرير قيمة hostPath للبرامتر الاول وهو مسار الملف وقيمة false للبارمتر الثاني append وهذا يعني انه سيتم محي بيانات الملف وكتابة بيانات جديدة بالكامل.

StreamWriter sw = new StreamWriter(hostPath, false);
for (int i = 0; i < dtSites.Rows.Count; i++)
{
    sw.WriteLine(dtSites.Rows[i]["ip"].ToString() + " " + dtSites.Rows[i]["url"].ToString());
}

    sw.Close();
    showData();
}
Dim sw As New StreamWriter(hostPath, False)
For k As Integer = 0 To (dtSites.Rows.Count - 1)
    sw.WriteLine(dtSites.Rows(k)("ip").ToString() & " " & dtSites.Rows(k)("url").ToString())
Next

sw.Close()
showData()

لكتابة البيانات الجديدة طبعاً يجب عمل حلقة للدوران على جميع الاسطر في الجدول ثم إضافتها إلى الملف بالتنسيق الصحيح ,,
بعد الانتهاء من عملية الحفظ نقوم باستدعاء الاجراء showData لإظهار البيانات الجديدة.
  
كود الزر إضافة في الفورم الثانية:                                                            

الزر إضافة تابع للفورم الثانية ووظيفته إضافة عنوان الموقع والآيبي للملف hosts:

نافذة الإضافة برنامج WebsiteBlocker VB.NET & C#
   
 
أولاً نقوم بجلب مسار الملف hosts عن طريق جلب مسار المجلد System32 ثم نكمل باقي المسار يدوياً:
string hostPath = Environment.GetFolderPath(Environment.SpecialFolder.System);
hostPath += @"\drivers\etc\hosts";
Dim hostPath As String = Environment.GetFolderPath(Environment.SpecialFolder.System)
hostPath &= "\drivers\etc\hosts"

ثم نقوم بإنشاء كائن StreamWriter مع تمرير قيمة true للبارمتر append وبهذا سيتم إضافة البيانات الجديدة إلى الملف ولكن مع بقاء البيانات السابقة. أي عكس الحالة السابقة.
StreamWriter sw = new StreamWriter(hostPath, true);
sw.WriteLine(txtIP.Text.Trim() + " " + txtURL.Text.Trim());

sw.Close();
Dim sw As New StreamWriter(hostPath, True)
sw.WriteLine(txtIP.Text.Trim() & " " & txtURL.Text.Trim())

sw.Close()
 
ثم نقوم بإضافة سطر جديد يحوي البيانات المطلوبة بالتنسيق المناسب وحسب نوع الحجب.
   
  
 
  

إرسال تعليق