מחולל Data URI

קידוד תמונה, גופן או טקסט ל-data: URI, או פענוח של אחד, עם מדידה משווה בין Base64 ל-percent-encoding. רץ בדפדפן שלך.

מקור
או גררו אחד לכאן — הוא נקרא מקומית ולעולם לא נשלח
תוצאה
data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%232563eb'%20stroke-width='2'%3E%3Ccircle%20cx='12'%20cy='12'%20r='9'/%3E%3Cpath%20d='M8%2012.5l2.5%202.5%205-6'/%3E%3C/svg%3E

Percent-encoding קצר יותר כאן: 255 תווים מול 272, חיסכון של 17.

גדלים

בייטים במקור
174
URI, ב-Base64
272
URI, ב-percent-encoding
255

קובץ בתוך כתובת

data: URI הוא קובץ שלם שנכתב כולו בתוך כתובת. במקום להצביע על משאב שהדפדפן צריך ללכת ולהביא, הוא נושא את הבייטים עצמם, כך שאייקון יכול לגור בתוך גיליון הסגנון שמשתמש בו ותמונה קטנה בתוך ה-HTML שמציג אותה. RFC 2397 הגדיר את הסכמה ב-1998, והיא נתמכת בכל מקום כבר שני עשורים.

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

data:[<mediatype>][;base64],<data>

data:,hello                          text/plain;charset=US-ASCII
data:text/plain;charset=utf-8,hello  the same bytes, spelled out
data:image/png;base64,iVBORw0KGgo=   binary, Base64 encoded
data:image/svg+xml,%3Csvg%20...      markup, percent-encoded

כותרת ריקה היא חוקית ומשמעותה text/plain;charset=US-ASCII, ברירת המחדל היחידה שהמפרט מעניק. הדף הזה מדווח שהוא החיל אותה במקום להציג סוג מדיה שמעולם לא כתבתם.

שני קידודים, ומי מהם קצר יותר

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

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

percent-encoding משאיר כל תו שחוקי בתוך כתובת בדיוק כפי שהוא, ומוציא שלושה תווים על כל תו שאינו. עבור מטען שהוא בעיקר ASCII פשוט — SVG, גיליון סגנון קטן, גוש JSON — רוב הבייטים עוברים ללא נגיעה והתוצאה קצרה מ-Base64, לא פעם בעשרים עד שלושים אחוזים. עבור PNG או גופן, שבהם כמעט כל בייט חייב המלטה, היא מתקרבת לפי שלושה מהמקור וגרועה בהרבה מ-Base64.

כמה ממלטים היא עצמה החלטה, והדף הזה מציע את שתי התשובות:

  • הסט המחמיר ממלט כל בייט שמחוץ לתווים ה-unreserved של RFC 3986 — אותיות, ספרות, מקף, נקודה, קו תחתון וטילדה. התוצאה בטוחה בכל הקשר שהוא, במחיר המלטה של סימני פיסוק שלא היו זקוקים לה.
  • הסט המינימלי שומר על כל מה שדקדוק ה-data URI באמת מתיר, ובכלל זה הלוכסנים, הנקודתיים, סימני השווה, הגרשים והסוגריים שסימון מלא בהם. כאן percent-encoding מנצח, ולכן SVG שנכתב עם גרש בודד סביב המאפיינים שלו מקודד הרבה יותר טוב מאחד שנכתב עם מרכאות כפולות.
  • היעד מוסיף כלל אחד נוסף. בתוך מאפיין HTML, אמפרסנד חשוף פותח הפניית תו, ולכן בחירת יעד ה-img ממלטת גם אותו. שום דבר אחר לא משתנה: המרכאה הכפולה, סוגריים המשולשים והלוכסן ההפוך נמצאים ממילא מחוץ לסט המותר וממולטים בשתי הרמות.
  • סימן אחוז ממולט תמיד, בכל רמה. הוא פותח רצף המלטה, ולכן אחוז מילולי חייב להיכתב %25 אחרת שני התווים הבאים נקראים כהקסדצימלי.

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

ה-33% הם מספר שלפני דחיסה

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

Base64 אינו מוסיף מידע; הוא פורש את אותו מידע על פני יותר תווים, ומשתמש רק ב-64 מתוך 256 הערכים שבייט יכול להחזיק. זו בדיוק סוג היתירות ש-gzip ו-Brotli קיימים כדי להסיר. על מטען שכבר דחוס — PNG, JPEG, גופן WOFF2 — הדוחס אינו יכול לגעת בנתונים עצמם אבל כן יכול לארוז את ה-Base64 בחזרה לגודל קרוב מאוד למקורי. אותו שליש מפורסם ברובו נעלם.

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

המחירים האמיתיים נמצאים במקום אחר. קובץ מוטמע אינו ניתן לשמירה בקאש בפני עצמו, ולכן הוא יורד שוב עם כל עותק של המסמך שנושא אותו, ולעולם לא ישותף בין שני דפים. הוא גם חוסם: גיליון סגנון אינו מסיים להיקרא עד שקרא את כל ה-URI שיושב באמצעו. HTTP/2 ביטל את רוב העלות לבקשה שהפכה את ההטמעה למושכת מלכתחילה. עבור אייקון זעיר שמופיע בכל דף זו עדיין עסקה סבירה; מעבר לכמה עשרות קילובייטים בדרך כלל לא, ולכן הדף הזה מתחיל להזהיר בשלושים.

הסוג נקבע לפי הבייטים

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

כשגוררים קובץ פנימה, הדפדפן מדווח על סוג משלו, אבל הדיווח הזה נגזר מסיומת הקובץ ומשום דבר אחר. שנו שם של JPEG ל-‎.png והדפדפן יקרא לו PNG. הדף הזה קורא במקום זאת את הבייטים הראשונים. כמעט כל פורמט בינארי פותח בחתימה: PNG מתחיל בבייט שאינו יכול להופיע ב-ASCII ואחריו האותיות PNG, JPEG בשלושה בייטים קבועים, PDF בסימן אחוז והמילה PDF, ו-WOFF ו-WOFF2 בתגיות ארבע-תוויות משלהם. כשהחתימה והסיומת סותרות זו את זו, החתימה מנצחת והסתירה מדווחת.

הרשימה קצרה במכוון — הפורמטים שאנשים באמת מטמיעים, ולא כל מה שדפדפן יודע לרחרח. אם שום דבר לא מתאים והבייטים הם טקסט תקין, הסוג הוא טקסט, כאשר SVG ו-XML מופרדים לפי מה שהסימון פותח בו. אם שום דבר לא מתאים בכלל, התשובה היא application/octet-stream, והשדה נשאר ניתן לעריכה, כי ניחוש שגוי גרוע מהודאה.

עבור מטענים טקסטואליים גם מערך התווים חשוב. ברירת המחדל של המפרט היא US-ASCII, שאינה מה שמישהו מתכוון אליו היום, ולכן סוג מדיה טקסטואלי מוצע עם charset=utf-8 מוצמד. השמטתו אינה משחיתה את הבייטים, אבל היא כן משנה איך קוראים אותם בחזרה.

לקרוא אחד בחזרה

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

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

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

דבר אחד שנראה דומה נדחה על הסף. אלפבית ה-Base64 הידידותי-לכתובת, שמחליף פלוס ולוכסן במקף ובקו תחתון, הוא מה ש-JSON Web Token משתמש בו — והוא אינו מה ש-data: URI מקבל. קבלה שקטה שלו הייתה אומרת פענוח לבייטים אחרים ממה שדפדפן היה מפענח, ולכן הוא נדחה בשמו, עם המיקום של התו הפוגע.

איפה זה רץ

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

שאלות נפוצות

האם הקובץ שלי נשלח לאנשהו?
לא. הקובץ נקרא מקומית באמצעות ממשק הקבצים של הדפדפן, מקודד על המחשב שלכם ולעולם אינו נשלח לשום מקום. אותו דבר נכון לגבי URI שאתם מדביקים כדי לפענח.
להשתמש ב-Base64 או ב-percent-encoding?
במה שקצר יותר עבור המטען שלכם, וזה מה שהדף מודד בשבילכם. ככלל אצבע: percent-encoding עבור SVG, CSS, JSON וטקסט אחר, ו-Base64 עבור תמונות, גופנים וכל דבר שכבר דחוס. ההפרש הוא לא פעם עשרים עד שלושים אחוזים לכיוון זה או אחר.
האם Base64 באמת מגדיל קובץ ב-33%?
לפני דחיסה, כן: ארבעה תווים לכל שלושה בייטים. על הקו, בעיקר לא. Base64 משתמש רק ב-64 מתוך 256 ערכי בייט אפשריים, והיתירות הזו היא בדיוק מה ש-gzip מסיר, ולכן הגודל הדחוס בדרך כלל קרוב לגודל הדחוס של הקובץ המקורי. הדף מודד את שניהם כדי שתראו את זה.
כמה גדול יכול להיות data URI?
דפדפנים מודרניים אינם מטילים תקרה קשיחה על URI בתוך מסמך, אם כי גרסאות ישנות של Internet Explorer הגבילו ל-32 קילובייט. הגודל הוא שאלה של ביצועים ולא של חוקיות: קובץ מוטמע אינו ניתן לשמירה בקאש בנפרד ויורד מחדש עם כל עותק של המסמך. הדף הזה מקודד עד מגה-בייט ומזהיר מעל שלושים קילובייט.
למה ה-data URI של ה-SVG שלי נשבר ב-CSS?
כמעט תמיד תו שלא מולט. סולמית — מצבע כמו ‎#2563eb — פותחת מזהה עוגן וקוטעת את ה-URI באותה נקודה. מרכאה כפולה מסיימת את מחרוזת ה-CSS שהיא יושבת בתוכה. כתבו את ה-SVG עם גרש בודד סביב המאפיינים והשתמשו בסט ההמלטה המינימלי, שממלט את הסולמית ואת המרכאה הכפולה ומשאיר את כל השאר קצר.
אפשר להשתמש ב-data URI לסקריפט או ל-iframe?
אפשר, אבל התייחסו לזה כשאלת אבטחה ולא כנוחות. data: URI אינו יורש מקור, ודפדפנים כבר חוסמים ניווט ראשי אליו בדיוק בגלל זה. מדיניות Content Security Policy שמתירה data: ב-script-src או ב-frame-src מוותרת על רוב מה שהמדיניות באה להגן עליו; התרה ב-img-src או ב-font-src היא עניין שגרתי.
למה ה-Base64 הידידותי-לכתובת שלי נדחה?
כי data: URI מקבל Base64 תקני. האלפבית הידידותי-לכתובת מחליף פלוס ולוכסן במקף ובקו תחתון, שהם תווים אחרים שמתפענחים לבייטים אחרים. קבלה שלו הייתה אומרת שהדף הזה מפענח את ה-URI שלכם אחרת מהדפדפן שבאמת יטען אותו.
הדפדפן אומר שהקובץ שלי מסוג אחד והדף הזה אומר אחר. מי צודק?
הדף. דפדפן מדווח על הסוג שהוא מסיק מסיומת הקובץ, ולכן קובץ ששמו שונה מדווח בטעות. הדף הזה קורא את החתימה בתחילת הקובץ, שהיא מה שהפורמט עצמו מצהיר. השדה נשאר ניתן לעריכה אם יש לכם סיבה לדרוס אותו.