@@ -27,20 +27,45 @@ var iUp = (function () {
2727 } ;
2828} ) ( ) ;
2929
30+ // Bing image URL pattern: validates format and prevents CSS injection
31+ var BING_IMAGE_URL_PATTERN = / ^ \/ t h \? i d = O H R \. [ a - z A - Z 0 - 9 _ \- ] + \. j p g ( & [ a - z A - Z 0 - 9 = . _ \- ] + ) * $ / ;
32+
3033function getBingImages ( imgUrls ) {
3134 /**
3235 * 获取Bing壁纸
3336 * 先使用 GitHub Action 每天获取 Bing 壁纸 URL 并更新 images.json 文件
3437 * 然后读取 images.json 文件中的数据
3538 */
36- var indexName = "bing-image-index" ;
37- var index = sessionStorage . getItem ( indexName ) ;
3839 var panel = document . querySelector ( '#panel' ) ;
39- if ( isNaN ( index ) || index == 7 ) index = 0 ;
40- else index ++ ;
40+ if ( ! panel || ! imgUrls || ! Array . isArray ( imgUrls ) || imgUrls . length === 0 ) {
41+ return ;
42+ }
43+
44+ var indexName = "bing-image-index" ;
45+ var index = parseInt ( sessionStorage . getItem ( indexName ) , 10 ) ;
46+ var maxIndex = imgUrls . length - 1 ;
47+
48+ if ( isNaN ( index ) || index > maxIndex ) {
49+ index = 0 ;
50+ } else {
51+ index ++ ;
52+ if ( index > maxIndex ) {
53+ index = 0 ;
54+ }
55+ }
56+
4157 var imgUrl = imgUrls [ index ] ;
58+ // Validate URL format to prevent CSS injection
59+ if ( ! imgUrl || typeof imgUrl !== 'string' || ! imgUrl . match ( BING_IMAGE_URL_PATTERN ) ) {
60+ return ;
61+ }
62+
63+ // Use backgroundImage property with proper escaping to prevent CSS injection
4264 var url = "https://www.cn.bing.com" + imgUrl ;
43- panel . style . background = "url('" + url + "') center center no-repeat #666" ;
65+ panel . style . backgroundImage = "url('" + url . replace ( / [ ' \\ ] / g, '\\$&' ) + "')" ;
66+ panel . style . backgroundPosition = "center center" ;
67+ panel . style . backgroundRepeat = "no-repeat" ;
68+ panel . style . backgroundColor = "#666" ;
4469 panel . style . backgroundSize = "cover" ;
4570 sessionStorage . setItem ( indexName , index ) ;
4671}
@@ -52,56 +77,70 @@ function decryptEmail(encoded) {
5277
5378document . addEventListener ( 'DOMContentLoaded' , function ( ) {
5479 // 获取一言数据
55- var xhr = new XMLHttpRequest ( ) ;
56- xhr . onreadystatechange = function ( ) {
57- if ( this . readyState == 4 && this . status == 200 ) {
58- var res = JSON . parse ( this . responseText ) ;
59- document . getElementById ( 'description' ) . innerHTML = res . hitokoto + "<br/> -「<strong>" + res . from + "</strong>」" ;
60- }
61- } ;
62- xhr . open ( "GET" , "https://v1.hitokoto.cn" , true ) ;
63- xhr . send ( ) ;
80+ fetch ( "https://v1.hitokoto.cn" )
81+ . then ( function ( response ) {
82+ return response . json ( ) ;
83+ } )
84+ . then ( function ( res ) {
85+ var descElement = document . getElementById ( 'description' ) ;
86+ if ( descElement && res . hitokoto && res . from ) {
87+ // Create text nodes to prevent XSS
88+ var textNode = document . createTextNode ( res . hitokoto ) ;
89+ var br = document . createElement ( 'br' ) ;
90+ var fromText = document . createTextNode ( ' -「' ) ;
91+ var strong = document . createElement ( 'strong' ) ;
92+ strong . textContent = res . from ;
93+ var endText = document . createTextNode ( '」' ) ;
94+
95+ descElement . innerHTML = '' ;
96+ descElement . appendChild ( textNode ) ;
97+ descElement . appendChild ( br ) ;
98+ descElement . appendChild ( fromText ) ;
99+ descElement . appendChild ( strong ) ;
100+ descElement . appendChild ( endText ) ;
101+ }
102+ } )
103+ . catch ( function ( error ) {
104+ console . error ( 'Error fetching hitokoto:' , error ) ;
105+ } ) ;
64106
65107 var iUpElements = document . querySelectorAll ( ".iUp" ) ;
66- iUpElements . forEach ( function ( element ) {
67- iUp . up ( element ) ;
68- } ) ;
108+ for ( var i = 0 ; i < iUpElements . length ; i ++ ) {
109+ iUp . up ( iUpElements [ i ] ) ;
110+ }
69111
70112 var avatarElement = document . querySelector ( ".js-avatar" ) ;
71- avatarElement . addEventListener ( 'load' , function ( ) {
72- avatarElement . classList . add ( "show" ) ;
73- } ) ;
113+ if ( avatarElement ) {
114+ avatarElement . addEventListener ( 'load' , function ( ) {
115+ avatarElement . classList . add ( "show" ) ;
116+ } ) ;
117+ }
74118} ) ;
75119
76120var btnMobileMenu = document . querySelector ( '.btn-mobile-menu__icon' ) ;
77121var navigationWrapper = document . querySelector ( '.navigation-wrapper' ) ;
78122
79- btnMobileMenu . addEventListener ( 'click' , function ( ) {
80- if ( navigationWrapper . style . display == "block" ) {
81- navigationWrapper . addEventListener ( 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend' , function ( ) {
82- navigationWrapper . classList . toggle ( 'visible' ) ;
83- navigationWrapper . classList . toggle ( 'animated' ) ;
84- navigationWrapper . classList . toggle ( 'bounceOutUp' ) ;
85- navigationWrapper . removeEventListener ( 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend' , arguments . callee ) ;
86- } ) ;
87- navigationWrapper . classList . toggle ( 'animated' ) ;
88- navigationWrapper . classList . toggle ( 'bounceInDown' ) ;
89- navigationWrapper . classList . toggle ( 'animated' ) ;
90- navigationWrapper . classList . toggle ( 'bounceOutUp' ) ;
91- } else {
92- navigationWrapper . classList . toggle ( 'visible' ) ;
93- navigationWrapper . classList . toggle ( 'animated' ) ;
94- navigationWrapper . classList . toggle ( 'bounceInDown' ) ;
95- }
96- btnMobileMenu . classList . toggle ( 'social' ) ;
97- btnMobileMenu . classList . toggle ( 'iconfont' ) ;
98- btnMobileMenu . classList . toggle ( 'icon-list' ) ;
99- btnMobileMenu . classList . toggle ( 'social' ) ;
100- btnMobileMenu . classList . toggle ( 'iconfont' ) ;
101- btnMobileMenu . classList . toggle ( 'icon-angleup' ) ;
102- btnMobileMenu . classList . toggle ( 'animated' ) ;
103- btnMobileMenu . classList . toggle ( 'fadeIn' ) ;
104- } ) ;
123+ if ( btnMobileMenu && navigationWrapper ) {
124+ btnMobileMenu . addEventListener ( 'click' , function ( ) {
125+ var isVisible = navigationWrapper . classList . contains ( 'visible' ) ;
126+
127+ function handleAnimationEnd ( ) {
128+ navigationWrapper . classList . remove ( 'visible' , 'animated' , 'bounceOutUp' ) ;
129+ navigationWrapper . removeEventListener ( 'animationend' , handleAnimationEnd ) ;
130+ }
131+
132+ if ( isVisible ) {
133+ navigationWrapper . addEventListener ( 'animationend' , handleAnimationEnd ) ;
134+ navigationWrapper . classList . remove ( 'bounceInDown' ) ;
135+ navigationWrapper . classList . add ( 'animated' , 'bounceOutUp' ) ;
136+ } else {
137+ navigationWrapper . classList . add ( 'visible' , 'animated' , 'bounceInDown' ) ;
138+ }
139+
140+ btnMobileMenu . classList . toggle ( 'icon-list' ) ;
141+ btnMobileMenu . classList . toggle ( 'icon-angleup' ) ;
142+ } ) ;
143+ }
105144
106145function showWeChatModal ( ) {
107146 const modal = document . getElementById ( 'wechatModal' ) ;
0 commit comments