הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

לימוד לא לינארי

21/10/2023

לימוד לינארי זה מה שקורה בכיתה - גם ברמת השיעור וגם ברמת השנה. לימוד לינארי זה לשבת ולהקשיב למסלול שמישהו אחר בנה בשבילך, לפתור בכל שלב את התרגילים שילמדו אותך את המיומנות של אותו שלב ומתישהו להצליח לחבר את כל הנקודות כדי לבנות מוצר.

בלימוד לינארי לא צריך לחשוב מה הדבר הבא שצריך לעשות. מספיק להיכנס לכיתה או ללחוץ Play על הוידאו הבא. גם אין פערי ידע (לפחות בתיאוריה). אם סיימת שלב קיבלת גם את כל המיומנות של אותו השלב דרך הקשבה לשיעור ופיתרון תרגילים. ואפילו אם לא מבינים משהו, כל מה שצריך לעשות זה לדפדף אחורה בספר או לצפות בסרטי וידאו קודמים שוב כדי לחזק את היסודות ואז אפשר יהיה להמשיך.

גם מורים שבונים שיעורים בצורה לינארית יגלו שהחיים שלהם לא מאוד מסובכים. נכון צריך לשבור את הראש ולבחור מה ללמד ובאיזה סדר, אבל אחרי מספיק פעמים שתלמדו משהו אתם תגלו שאפשר לעשות אופטימיזציה לחווית הלמידה ואפשר לבנות מסלול לינארי שידבר לאנשים.

אז איפה בכל זאת הבעיה? שני אתגרים-

  1. הראשון הוא שאנחנו לא לומדים כך. מאוד קשה לבני אדם ללמוד משהו באמצעות "בניה מלמטה" של שכבות כמו שבונים בניין. הלימוד האנושי הוא ספיראלי, וכל פעם שאנחנו מגלים פיסות מידע חדשות אנחנו מחברים את הידע החדש לדברים שאנחנו יודעים, ודרך הידע החדש מגלים דברים חדשים על השכבות שכביכול למדנו. פה באתר אני פוגש לא מעט אנשים שמסיימים קורס וידאו ואחרי כמה חודשים של עבודה וניסיון חוזרים לצפות בו פעם נוספת ומגלים דברים חדשים לגמרי.

  2. והאתגר השני הוא שהמציאות לא בנויה בצורה לינארית. יש פשוט יותר מדי חומר, יותר מדי דברים ללמוד וכל ניסיון ללמוד את הכל מההתחלה בצורה מסודרת חייב לדלג על נושאים חשובים רק בשביל להגיע לאיזושהי מיומנות בעלת ערך.

אם אתם רוצים לשפר את אפקטיביות הלמידה שלכם, נסו להחליף את הלימוד הלינארי בלימוד ספיראלי, כלומר התחילו בלימוד מהיר ממש דרך דפדוף בכמה דוגמאות, לאחר מכן בנו כמה דברים קטנים (ממה שאתם מצליחים) עם מה שלמדתם ואז חזרו לסיבוב נוסף בו תתעמקו באותן דוגמאות עליהן עברתם בחטף ואת התיאוריה מאחוריהן. בעזרת הידע החדש תוכלו לשפר את הקוד שכתבתם וכך המשיכו בסבבים, כולל הרחבה של הלופ מדי פעם כדי להכיר דוגמאות חדשות וטכניקות חדשות לשימוש באותה טכנולוגיה.

הפוסט שלא כתבתי

20/10/2023

הפוסט שלא כתבתי היה אמור לספר על בעיה ממש מתוחכמת. כל כך מסובכת שבזמן אמת אפילו לא הבנתי אותה עד הסוף. וכמובן על הפיתרון הסופר מתוחכם שלה, שבהתחלה לא עבד ורק אחרי כמה ניסיונות הצלחתי להגיע לנוסחה שבדיוק פתרה את הבעיה.

הפוסט שלא כתבתי היה אמור לספר לכם על הגורם האמיתי לבעיה הזאת, ואת כל הדרך שעשיתי כדי לגלות אותה. וגם להראות את כל הפיתרונות שלא עבדו ולהסביר מה היו הבעיות שלהם, ואיך הפיתרון שעבד (אפילו שהוא מאוד מסובך) קלע כמו חץ למטרה ובדיוק פתר את הבאג.

הבעיה עם הפוסט שלא כתבתי היתה שתוך כדי חיפוש כל ההסברים האלה פתאום הבנתי שהבעיה היתה הרבה יותר פשוטה ממה שחשבתי בהתחלה. כל כך פשוטה שאין אפילו מה לכתוב עליה. והפיתרון הסופר מסובך שלקח לי שעות לכתוב? הוא התגלה כ Workaround טפשי שאפילו לא מטפל בכל המקרים. המחקר לקראת הפוסט שלא כתבתי מוטט את כל הקוד המסובך כמו מגדל קלפים, והשאיר אותי ללא פוסט אבל עם קוד פשוט שעובד הרבה יותר טוב.

הפוסט שלא כתבתי מזכיר לי שהכתיבה על קוד הכרחית בדיוק כמו כתיבת הקוד.

לא Java, מחרוזת וביטוי רגולארי זה שני דברים שונים

19/10/2023

ל Java יש API לעבודה עם ביטויים רגולאריים דרך החבילה java.util.Pattern. עם החבילה הזאת אפשר לכתוב דברים כמו:

Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();

או אפילו להשתמש בפונקציות הסטטיות ולכתוב:

boolean b = Pattern.matches("a*b", "aaaaab");

וביום רגיל היינו עוצרים פה את השיחה על ביטויים רגולאריים וחוזרים לדבר על קוד. אבל עם Java שום יום אינו יום רגיל.

מחוץ לחבילת הביטויים הרגולאריים יש חבילה של מחרוזות ובה הפונקציה split:

public String[] split(String regex)

וכן שם הפרמטר נבחר בכוונה - ג'אווה מתיחס למחרוזת ההפרדה ל split כמו ביטוי רגולארי. קוד? בטח. הנה שתי דוגמאות:

import java.util.Arrays;

class Main {
  public static void main(String[] args) {
    // prints: [hello, world]
    System.out.println(Arrays.toString("hello-world".split("-")));

    // prints: []
    System.out.println(Arrays.toString("hello.world".split(".")));
  }
}

השורה הראשונה מדפיסה מערך עם שני איברים, הראשון הוא המילה hello והשני המילה world. השורה השניה מחזירה מערך ריק, כי נקודה היא ביטוי רגולארי שמתאים לכל תו, ולכן כל תו הוא תו הפרדה ולכן לא נשארו דברים אמיתיים (מופרדים) לשים במערך.

משתני קונטקסט בסקאלה

18/10/2023

מבנה אחד שחוזר על עצמו בכתיבת קוד פונקציונאלי הוא הרעיון של העברת משתנה אחד להרבה פונקציות, בדרך כלל כשהמשתנה שומר קונפיגורציה. למשל בבלוק הבא בקלוז'ר שמדגים איך להשתמש בספריה clj-ssh:

(let [agent (ssh-agent {})]
  (let [session (session agent "host-ip" {:strict-host-key-checking :no})]
    (with-connection session
      (let [channel (ssh-sftp session)]
        (with-channel-connection channel
          (sftp channel {} :cd "/remote/path")
          (sftp channel {} :put "/some/file" "filename"))))))

בבלוק מעתיקים קבצים לשרת ואנחנו רואים שצריך להעביר את המשתנה channel כל פעם שקוראים ל sftp. זה הגיוני כי אנחנו בשפה פונקציונאלית והסטייט נשמר מחוץ לפונקציות, אבל יכול להיות מעייף בכתיבה.

סקאלה, שמהתרשמותי עד כה מנסה להיות שפה פונקציונאלית ידידותית גם אנשים שאינם פונקציונאליים, כוללת מנגנון מעניין כדי לחסוך כפילויות מסוג זה, מנגנון שנקרא given / using. למעשה מנגנון ה given וה using של סקאלה הרבה יותר מתוחכם ממה שאני מראה פה, אבל המקרה פה בפוסט הוא דוגמה לשימוש אחד יחסית קל להבנה במנגנון זה.

איך זה עובד? בהגדרת פונקציות בסקאלה אנחנו יכולים להגדיר בנוסף לפרמטרים הרגילים שעוברים לפונקציה גם משתני "קונטקסט". הדרך הכי קלה לחשוב עליהם היא בתור "משתני סביבה" של הבלוק הספציפי בתוכנית. לדוגמה כך נראית הגדרה של פונקציה המקבלת מידע על שרת בפרמטר בשם config:

def readUsers(config: ServerConfig): Unit =
  println(s"read users from ${config.ip}")

וכך נראית אותה פונקציה כשהפרמטר הזה יכול לעבור בתור "משתנה סביבה":

def readUsers(using config: ServerConfig): Unit =
  println(s"read users from ${config.ip}")

המילה using מגדירה לסקאלה שמשתנה זה יכול להגיע מהסביבה ולא תמיד חייבים להעביר אותו כפרמטר בקריאה לפונקציה. בשביל להגדיר שמשתנה מסוים זמין בסביבה לפונקציות כמו readUsers אני משתמש במילה השמורה given. לכן הפעלת הפונקציה נראית כך:

@main def main(): Unit =
  given config: ServerConfig(ip = "12.13.14.15", port = 8080)
  readUsers

הדוגמה הופכת מעניינת כשאני מוסיף עוד פונקציות, כולל כאלה שמקבלות פרמטרים:

def readUsers(using config: ServerConfig): Unit =
  println(s"read users from ${config.ip}")

def addUser(username: String, password: String)(using config: ServerConfig): Unit =
  println(s"add user ${username} to ${config.ip}")

def wrapper(using ServerConfig): Unit =
  readUsers

@main def main(): Unit =
  given config: ServerConfig(ip = "12.13.14.15", port = 8080)

  readUsers
  addUser("foo", "bar")
  wrapper

בזכות הגדרת ה given בתחילת ה main, כל הפונקציות שצריכות משתנה סביבה מסוג ServerConfig יקבלו את אוביקט הקונפיגורציה שיצרתי. יותר מזה, הפוקנציה wrapper יכולה לקרוא ל addUsers בלי שאף אחד מהן תצטרך לקבל או להעביר את משתנה הקונפיגורציה ופשוט כל פונקציה שצריכה לגשת אליו מקבלת אותו אוטומטית ובצורה מובלעת.

נכון, הפיצ'ר הזה לא ייחודי לסקאלה ולדוגמה בקלוז'ר יש מנגנון דומה שבנוי על משתנים גלובאליים ושינוי ההצבעה שלהם. הדוגמה הבאה בקלוז'ר ממחישה את הרעיון:

(def ^:dynamic *config* {:ip "default-ip"})

(defn read-users []
  (println (str "read users from " (:ip *config*))))

;; Usage
(let [custom-config {:ip "custom-ip"}]
  (binding [*config* custom-config]
    (read-users)))

ההבדל המרכזי בין הדוגמה בסקאלה לדוגמה בקלוז'ר הוא שבסקאלה משתנה הקונטקסט הוא ממש חלק מהגדרת הפונקציה, והקומפיילר דואג להעביר את הערך הנכון בקריאה (בדיוק כמו בקריאה רגילה לפונקציה), בעוד שבקלוז'ר עלינו להגדיר משתנה גלובאלי ושינוי הערך שלו קורה בזמן ריצה בצורה דינמית. לכן בקלוז'ר אם הפעלתי את הפונקציה בלי ה binding אני מקבל תוצאה לא נכונה, אבל בסקאלה הקוד פשוט לא מתקמפל.

האם מפתחים חייבים להפיץ תוכנות מאובטחות?

17/10/2023

האיזון בין חובת המפתחים להפצת תוכנות מאובטחות לבין חופש של מפתחים לכתוב ולהפיץ תוכנות ללא דאגות נמצא בימים אלה בדיונים סופיים בפרלמנט האירופי, שכבר עובד על הצעה שנמצאת בשלבים סופיים של אישורים בשם Cyber Resilience Act. מטרת ההצעה היא להבהיר למפתחים ולמשתמשים מי אחראי לתיקון בעיות אבטחה ובדיוק באיזה אופן.

המשך קריאה

מה כותבים קודם - ממשק משתמש או לוגיקה?

16/10/2023

לכל אופציה יש את היתרונות והחסרונות שלה, ולפעמים התשובה מורכבת.

לכתוב את ממשק המשתמש לפני הלוגיקה אומר שאני מכניס נתונים פיקטיביים, קבועים, למערכת, או בונה לוגיקה מאוד בסיסית רק כדי שאוכל להתקדם. ברגע שיש משהו על המסך אני יכול "לשחק" עם הממשק וחווית המשתמש עד שאני מגיע למשהו שעובד, ותוך כדי תנועה אני מעדכן את הלוגיקה ולאט לאט עובר לנתונים אמיתיים.

לכתוב את הלוגיקה לפני ממשק המשתמש אומר שאני משקיע המון עבודה מראש בכתיבת סכימות לבסיסי נתונים, שאילתות, קוד צד שרת ובדיקות עליו ורק אחרי ששכבת המידע בנויה אני ממשיך לבנות את ממשק המשתמש. את שכבת המידע בדקתי עם תוכניות בדיקה קטנות או בדיקות יחידה כדי שלא יהיו הפתעות כשאגיע לכתוב את ה GUI.

היתרונות של כתיבת ממשק המשתמש קודם הם לכן:

  1. אני לא כותב לוגיקה מיותרת אלא רק את המנגנונים שהמוצר והמשתמשים שלו צריכים.

  2. אני יכול לארגן את שכבת הלוגיקה כדי שממשק המשתמש יהיה קל לפיתוח, ויכול לבנות על זה שיש לי תמיד את הנתונים שאני צריך.

  3. אני מגיע יחסית מהר ללקוחות או למנהלים עם מוצר שהם יכולים לראות, מה שמאפשר לי לקבל פידבק בשלב מוקדם של התהליך.

אבל זה לא כל הסיפור. היתרונות של פיתוח שכבת המידע קודם הם:

  1. מימוש שכבת מידע יציבה עם בדיקות מבטיח שיהיו לי פחות באגים בשכבת המידע או לפחות שיהיה קל יותר למצוא ולתקן אותם.

  2. אחרי שיש ממשק משתמש ופידבק עליו, אנחנו מבלים המון זמן בהתאמת המוצר לפידבק מהלקוחות (בצדק) ולא מספיק זמן בבניית שכבת מידע יציבה.

  3. אילוצים שהגיעו מממשק המשתמש עלולים לגרום לנו לשמור מידע בצורה לא יעילה או מידע כפול, וכשנבין שאנחנו רוצים לשנות את מבנה הטבלאות זה כבר יהיה מאוחר מדי.

  4. הרבה יותר קל לשנות ממשק משתמש כשיש שכבת מידע יציבה מאשר לשנות שכבת מידע כשיש ממשק משתמש יציב.

קצת כמו עם TDD אני חושב שהבעיה המרכזית בפיתוח ממשק משתמש קודם היא שאחרי שיש ממשק ומוצר קשה "לחזור אחורה" כדי לכתוב את הלוגיקה כמו שצריך, ואז אנחנו תקועים עם מבנה טבלאות לא אופטימלי שמאט אותנו במימוש פיצ'רים חדשים.

פיתרון טוב וקשה לביצוע יהיה לחלק את הפיתוח לשלבים. בשלב הראשון נבנה ממשק משתמש עם שכבת מידע מנוונת רק בשביל להבין את דרישות הפרויקט, ובשלב השני אחרי שיש משהו באוויר נחזור לשולחן השרטוטים ונבנה מחדש את שכבת המידע מאפס בצורה יציבה יותר שלוקחת בחשבון את כל הפיצ'רים שאנחנו עכשיו יודעים שצריכים ומנגנוני ריפקטורינג ובדיקות שיעזרו לנו להמשיך ולתקן תוך כדי תנועה. וכן זה מדכא כי אי אפשר באמת לבנות מחדש רק את שכבת המידע בלי לגעת בקוד הממשק ולכן תהליך כזה הוא כמעט כתיבה מחדש של הפרויקט בפעם השניה.

בואו נסדר טקסט בטבלה עם הפקודה column

15/10/2023

הפקודה column נועדה כדי לקבל טקסט ארוך ולשבור אותו לעמודות כדי שיהיה קל יותר לקרוא אותו בלי לגלול, כמו טורים בדף של עיתון. אפשר לראות דוגמה קלה לשימוש בה כשנעביר אליה תוכן של קובץ. כך נראה הקובץ /etc/shells אצלי:

$ cat /etc/shells

# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/dash
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
/usr/local/bin/pwsh

וכך זה נראה עם column:

$ cat /etc/shells | column

# List of acceptable shells for chpass(1).                      /bin/csh                                                        /bin/tcsh
# Ftpd will not allow users to connect who are not using        /bin/dash                                                       /bin/zsh
# one of these shells.                                          /bin/ksh                                                        /usr/local/bin/pwsh
/bin/bash                                                       /bin/sh

אבל ל column יש שימוש נוסף אפילו יותר מעניין - המתג -t שלו גורם לפקודה לזהות קלט שמחולק לעמודות ולהדפיס בצורה יפה (כלומר מרווחת) קלט זה. לדוגמה ניקח את תוכנית ה bash הבאה שמדפיסה את לוח הכפל, בלי לסדר יפה את הפלט:

#!/bin/bash

end=${1:-10}

for ((i=1; i <= $end; i++))
do
  for ((j=1; j <= $end; j++))
  do
    printf "%d " $((i * j))
  done
  echo
done

ברירת המחדל היא להדפיס עד 10 אבל אפשר להעביר פרמטר כדי לקבל לוחות גדולים יותר. ונכון אפשר להתאים את ה printf כדי שהפלט יודפס מרווח יפה במקרה הזה אבל אנחנו רוצים לדבר על column. הפעלה רגילה של התוכנית מדפיסה:

1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100

התוספת של column -t כבר עושה את הקסם:

$ bash table.bash | column -t

1   2   3   4   5   6   7   8   9   10
2   4   6   8   10  12  14  16  18  20
3   6   9   12  15  18  21  24  27  30
4   8   12  16  20  24  28  32  36  40
5   10  15  20  25  30  35  40  45  50
6   12  18  24  30  36  42  48  54  60
7   14  21  28  35  42  49  56  63  70
8   16  24  32  40  48  56  64  72  80
9   18  27  36  45  54  63  72  81  90
10  20  30  40  50  60  70  80  90  100

דבר אחד שאולי מציק כאן זה היישור לשמאל. בגירסת ה BSD של column שמותקנת אצלי על המק צריך פשוט לחיות עם זה, אבל בלינוקס המתג -R0 גורם ליישור הטבלה כולה לימין וזה נראה כך:

ubuntu@primary:~/Home/tmp/blog$ bash table.bash |column -t -R0
 1   2   3   4   5   6   7   8   9   10
 2   4   6   8  10  12  14  16  18   20
 3   6   9  12  15  18  21  24  27   30
 4   8  12  16  20  24  28  32  36   40
 5  10  15  20  25  30  35  40  45   50
 6  12  18  24  30  36  42  48  54   60
 7  14  21  28  35  42  49  56  63   70
 8  16  24  32  40  48  56  64  72   80
 9  18  27  36  45  54  63  72  81   90
10  20  30  40  50  60  70  80  90  100

ואם התוצאה היתה מיידית?

14/10/2023

ואם אחרי הסיגריה הראשונה כבר היית חוטף סרטן ריאות?

ואם אחרי הגיוס הגרוע הראשון החברה היתה מתמוטטת?

ואם אחרי חור האבטחה הראשון האקרים היו מפילים את המערכת?

אנחנו ממש טובים בלזהות קשר בין סיבה לתוצאה כשתוצאות קורות בדיוק אחרי הסיבות שלהן, וככל שהזמן בין הסיבה לתוצאה מתארך קשה יותר לראות את זה. לקח הרבה שנים להבין שהסרטן ריאות קשור לסיגריות, לוקח הרבה זמן להבין שהירידה בביצועים היא בגלל ירידה באיכות כח האדם והמון זמן להבין שחורי האבטחה נגרמים בגלל תהליכי עבודה לא נכונים. כשהתוצאות קורות אנחנו עושים שתי טעויות:

  1. אנחנו מנסים לתקן את הבעיה באמצעות טלאי.

  2. אנחנו מוותרים על הטיפול בגורם האמיתי, כי תיקון בדרך זו ייקח המון זמן או כבר לא ריאלי (לתקן תהליכי עבודה? לגייס אנשים טובים יותר? כן אבל זה ייקח המון זמן, בואו נכבה קודם את השריפה).

הזמן הכי טוב לנטוע עץ היה לפני עשרים שנה. הזמן השני הכי טוב הוא עכשיו. לא בטוח שהתיקון הנכון יעבוד בזמן, אבל הוא הסיכוי היחיד שיש לנו.

קורס? ייעוץ? מה בעצם אני צריך?

13/10/2023

בייעוץ מגיעים אליכם לראות איך אתם עובדים ולתת טיפים שיעזרו לכם לעבוד בצורה יעילה יותר. היועץ יוכל להיכנס אתכם לעובי הקורה ולאתגרים הספציפיים של המערכת שלכם ולהציע שיפורים בתוך העולם שלכם.

בקורס אתם מגיעים למקום אחר, רואים שיטות עבודה שעוזרות לאנשים אחרים להתמודד עם אתגרים אחרים (דומים או שונים לאתגרים שלכם) ומקבלים רעיונות שאולי לא חשבתם שתצטרכו. בקורס הכינו עבורכם מסלול לימוד רוחבי שמכסה הרבה פיתרונות להרבה בעיות.

לכל שיטת עבודה יש את הייתרון שלה, אבל הדבר החשוב הוא לא לערבב. אין טעם לבוא לקורס אם יש לכם שיטת עבודה מאוד ספציפית וייחודית או שאתם מאוד מוטרדים בבעיות במערכת שלכם ולא פנויים לשמוע על מערכות אחרות ואתגרים אחרים. וגם להיפך - אין טעם לקחת יועץ כדי שיספר לכם מאפס על סוגי הפיתרונות האפשריים לבעיה מסוימת אם אתם עדיין לא מכירים את הבעיה.

הבנה נכונה של המצב שלכם ושל היעדים שלכם היא הצעד הראשון לפני שמחליטים איזה סוג עזרה לחפש.

תזכורת מ curl - מי ביקש ממך?

12/10/2023

הבאג שנחשף אתמול ב libcurl הוא כזה שמתכנתים אוהבים לעשות ואוהבים להתחרט עליו. היוצר של curl דניאל סטנברג פרסם את הפרטים. בגדול כשמושכים דף דרך libcurl אפשר להשתמש בפרוקסי, ואם משתמשים בפרוקסי אפשר לבחור לפענח לבד את שם השרת אליו פונים ולתת לפרוקסי את כתובת ה IP, או להעביר את שם השרת לפרוקסי שהוא ישבור את הראש.

יש לו גם משתנה בשם socks_proxy.proxytype שיכול לקבל את הערך CURLPROXY_SOCKS5 בשביל לפענח לבד את כתובת ה IP אליה צריך להתחבר, או את הערך CURLPROXY_SOCKS5_HOSTNAME בשביל להעביר את שם השרת ישירות לפרוקסי שהוא כבר ישבור את הראש. אבל במצב שצריך להעביר את שם השרת יש בעיה - שרתי socks תומכים רק בשמות שרת באורך עד 255 תווים.

עכשיו מה הייתם עושים אם משתמש בחר בשיטת עבודה לחיבור "שלח את שם השרת לפרוקסי" אבל שם השרת שהוא מנסה להתחבר אליו יצא ארוך יותר ממה שמתאפשר לשלוח?

דניאל עשה מה שרבים מאיתנו היו עושים ומתחרטים עליו בדיעבד - ברגע שהוא ראה שם שרת ארוך הוא הניח שמישהו העביר בטעות את הערך הלא נכון לפרמטר שבוחר מצב עבודה, ושינה את מצב העבודה לפיענוח שם שרת מקומי.

הבעיה שבגלל מבנה הקוד, השינוי לא משפיע על כל המצבים ואפשר לעקוף אותו. תוקף זדוני שרוצה להפיל את ה curl שלכם יכול לבנות שרת שישלח Redirect עם שם שרת ארוך מדי ובמצבים מסוימים יעקוף את שינוי מצב העבודה ויגרום ל Heap Overflow בקוד הלקוח.

הקוד הפגיע בגדול הוא:

  bool socks5_resolve_local =
    (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;
  const size_t hostname_len = strlen(sx->hostname);
  ssize_t len = 0;
  const unsigned char auth = data->set.socks5auth;
  bool allow_gssapi = FALSE;
  struct Curl_dns_entry *dns = NULL;

  DEBUGASSERT(auth & (CURLAUTH_BASIC | CURLAUTH_GSSAPI));
  switch(sx->state) {
  case CONNECT_SOCKS_INIT:
    if(conn->bits.httpproxy)
      infof(data, "SOCKS5: connecting to HTTP proxy %s port %d",
            sx->hostname, sx->remote_port);

    /* RFC1928 chapter 5 specifies max 255 chars for domain name in packet */
    if(!socks5_resolve_local && hostname_len > 255) {
      infof(data, "SOCKS5: server resolving disabled for hostnames of "
            "length > 255 [actual len=%zu]", hostname_len);
      socks5_resolve_local = TRUE;
    }

וזה פגיע כי שינוי הערך של המשתנה הבוליאני קורה רק במקרה של CONNECT_SOCKS_INIT, אבל אפשר להיכנס לפונקציה הזאת גם ישירות ל case-ים אחרים במצבים של שרת פרוקסי איטי.

המסקנה של דניאל נכונה גם לגבינו ולכל קוד שאנחנו כותבים. הוא כותב:

curl should not switch mode from remote resolve to local resolve due to too long host name. It should rather return an error and starting in curl 8.4.0, it does.

החלפת מצב הפעולה רק בגלל שגילינו ב init ששם השרת ארוך מדי היתה הטעות, כי משתמשים שביקשו מצב פעולה מסוים לא ציפו להתנהגות זו. זריקת שגיאה במקרה כזה היתה נותנת למשתמשים אפשרות לבחון טוב יותר את ה use case ולבחור את מצב הפעולה הטוב ביותר עבורם.