본문으로 건너뛰기

"jquery" 태그로 연결된 13개 게시물개의 게시물이 있습니다.

모든 태그 보기

jQuery 프로토타입 오염 취약점 (CVE-2019-11358)

· 약 2분

프로토타입 오염

이 공격은 JSON.parseObject.deepCopy 를 사용하는 모든 로직에서 발생할 수 있다.

2019-04-17 에 발표된 이 취약점은 $.extends 구문 상에서 프로토타입 XSS 공격이 가능하다.

예시

const myObject = '{"myProperty" : "a", "__proto__" : { "isAdmin" : true } }';
const newObject = $.extend(true, {}, JSON.parse(myObject));
console.log(newObject.isAdmin); // true

const temp = {};
console.log(temp.isAdmin); // true

패치

취약점 패치는 아래 부분을 변경해준다.

jQuery.extend = jQuery.fn.extend = function () {
// 사이 로직 생략
for (; i < length; i++) {
if ((options = arguments[i]) != null) {
for (name in options) {
src = target[name];
copy = options[name];

// [AS-IS]
// if (target === copy) {
// [TO-BE]
if (name === "__proto__" || target === copy) {
continue;
}

if (
deep &&
copy &&
(jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))
) {
if (copyIsArray) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}

target[name] = jQuery.extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}

return target;
};

Vue에서 jquery와 bootstrap 전역으로 사용하기

· 약 1분

expose-loader의 설치가 필요 없는 방법을 사용해보자

Vuejs-kr에 좋은 내용이 있지만 웹팩을 통해 jquery를 꺼내는 방법이 더 간단하다.

설치

yarn add jquery bootstrap

설정

webpack

build/webpack.base.conf.js
const webpack = require("webpack");

module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jquery: "jquery",
"window.jQuery": "jquery",
jQuery: "jquery",
}),
],
};

eslint

.enlintrs.js
module.exports = {
globals: {
$: true,
jQuery: true,
},
};

연동

src/main.js
import "bootstrap";

new Vue({});

jQuery Change Class

· 약 2분

Toggle Event 사용시 AAA 로 명명된 클래스를 BBB 로 바꾸고 싶을 때가 있다.

$("#id").removeClass("AAA").addClass("BBB");

보통 이런 방식으로 사용하는데, alterClass 함수를 곁들이면 더 나이스하게 바꿀 수 있다.

소스

/**
@author https://gist.github.com/peteboere/1517285
*/
$.fn.alterClass = function (removals, additions) {
if (removals.indexOf("*") === -1) {
// Use native jQuery methods if there is no wildcard matching
this.removeClass(removals);
return !additions ? this : this.addClass(additions);
}

const pattern = new RegExp(
"\\s" +
removals.replace(/\*/g, "[A-Za-z0-9-_]+").split(" ").join("\\s|\\s") +
"\\s",
"g",
);

this.each(function (i, it) {
let cn = " " + it.className + " ";
while (pattern.test(cn)) {
cn = cn.replace(pattern, " ");
}
it.className = $.trim(cn);
});

return !additions ? this : this.addClass(additions);
};

예제

// AAA to BBB
$("#id").alterClass("AAA", "BBB");
// AAA-12, BBB-13 to AAABBB
$("#id").alterClass("AAA-* BBB-*", "AAABBB");

**asterisk(*)**를 이용해 여러 개의 클래스를 한꺼번에 원하는 클래스로 변경할 수 있다.

여담

jQuery UI 를 사용 중이라면, .switchClass를 사용하면 된다.

jQuery serializeObject - form을 json으로 변환

· 약 2분

비동기 submit 을 진행시에 form 값을 확인해보고 json object 로 받아 한 번에 request 를 날리고 싶을 때 유용하다.

소스

/*
@author https://github.com/macek/jquery-serialize-object
*/
$.fn.serializeObject = function () {
"use strict";
const result = {};
const extend = function (i, element) {
const node = result[element.name];
if ("undefined" !== typeof node && node !== null) {
if ($.isArray(node)) {
node.push(element.value);
} else {
result[element.name] = [node, element.value];
}
} else {
result[element.name] = element.value;
}
};

$.each(this.serializeArray(), extend);
return result;
};

예제

const formData = $("#form").serializeObject();

여담

checkbox 같은 경우 여러 개 선택시 하나의 key 에 배열로 반환된다.

<form>
<input type="checkbox" name="arr" value="1" checked />
<input type="checkbox" name="arr" value="2" checked />
</form>

<script>
var formData = $("form").serializeObject();
// => formData = { arr : [1,2] };
</script>

사실 이 기능이 제일 매력적이다.

현재 serializeObject library는 더 높은 버전이 있다. 최신 버전에선 form 의 name 속성에 object 를 직접 관리할 수 있다. 최신 버전을 써도 괜찮고, 가볍게 추가해서 쓰고 싶으면 위 버전을 이용하자.

jQuery Enter Event

· 약 1분

로그인 폼을 만들 때 tab 과 enter 를 이용해 로그인 할 수 있게 해줘야한다.

소스

$("#id").keypress(function (e) {
if (e.which === 13) {
// do something
}
});

여담

로그인 폼을 매번 만들지 않기 때문에, e.which 는 매번 헷갈린다..

jQuery Opener Document 제어

· 약 1분

pure javascript

부모창을 제어하는 구문은 아래처럼 사용한다.

document.parent.getElementById("id");
window.opener.document.getElementById("id");

jQuery

jQuery를 사용하고 있다면 생각보다 쉽게 요소 선택을 할 수 있다.

const $id = $("#id", opener.document); // parent.document도 가능
$id.val("value");

2열처럼 바로 값 변경도 가능하다.

form.reset()의 input hidden 초기화 문제

· 약 1분

Javascript 의 form 의 reset() 메소드는 hidden field 와 check, radio button 에 대해 초기화를 시켜주지 않는다.

따라서 form 의 모든 field 를 초기화 시키려면 아래의 메소드가 필요하다.

소스

$.fn.clearForm = function () {
return this.each(function () {
const type = this.type,
tag = this.tagName.toLowerCase();
if (tag === "form") {
return $(":input", this).clearForm();
}
if (
type === "text" ||
type === "password" ||
type === "hidden" ||
tag === "textarea"
) {
this.value = "";
} else if (type === "checkbox" || type === "radio") {
this.checked = false;
} else if (tag === "select") {
this.selectedIndex = -1;
}
});
};

예제

$("#form").clearForm();

설명

여기의 clearForm 메소드를 hidden 도 초기화할 수 있게 커스터마이징 했다.

jQuery3의 큰 변경점

· 약 3분

jQuery3이 기존 버전에서 어떻게 달라졌는지 알아보자.

load(), unload(), error() 삭제

이 함수들은 이제 ajax 기능으로만 사용할 수 있다. 다음처럼 url 을 로드하는 데에만 사용할 수 있다.

// 사용 가능
$(div).load(url);

기존에 사용하던 엘레먼트 로드 시 callback 함수를 받는 구문은 더이상 사용할 수 없지만 on('load') 로 충돌을 피할 수 있다.

// 사용 불가능
$(img).load(function () {
console.log("이미지 로드 완료");
});

// 사용 가능
$(img).on("load", function () {
console.log("이미지 로드 완료");
});

document on Ready 삭제

document 로드시에 호출되는 함수들을 정의하기 위한 위의 형태는 더 이상 사용할 수 없다. $(document).ready(function(){ }); 으로 변경해도 되나

=> $(function(){ }); 로 사용하자, 권장하는 방법이다.

deferred의 Promise 스펙

Promise/A+ 스펙을 지키지 않은 2버전까지는 오류가 있었다고 하는데, then, when 등의 메소드를 사용 중엔 별다른 문제가 없어서 체감상 크게 느껴지지 않았다.

=> then().then().then().then().catch() 와 같은 구문이 가능하다.

bind(), unbind(), delegate() undelegate() 삭제

위 구문 사용시 console.warning이 떴기에 바꿔왔더라면 큰 문제는 없다.

=> on(), off() 로 대체하면 된다.

andSelf() 삭제

이 메소드가 삭제되어 sementic UI 사용시 오류가 발생한다.

=> addBack() 으로 대체하면 된다.

param() 이 %20 을 + 기호로 바꾸지 않음

$.ajax로 return 받을 때 가끔씩 빈칸이 더하기로 반환되는 문제가 드디어 해결되었다.

event.props, event.fixHooks 삭제

jquery.onoff 라이브러리 사용시 오류가 난다.

=> fixHooks는 fix로 대체 가능하고, props는 빈 배열로 초기화시키면 된다.

data attribute 정의시 kebab-case를 사용가능

이 부분은 jQuery 문서의 예제를 참조했다.

const $div = $("<div />");
$div.data("clickCount", 2);
$div.data("clickCount"); // 2
$div.data("click-count", 3);
$div.data("clickCount"); // 3
$div.data("click-count"); // 3

const allData = $div.data();
allData.clickCount; // 3
allData["click-count"]; // undefined
allData["click-count"] = 14;
$div.data("click-count"); // 3, NOT 14 as it would be in jQuery 2.x
allData.clickCount; // 3
allData["click-count"]; // 14

하지만 헷갈리니 camelCase 로.

기타 변경점은 링크 참조

jQuery Validation Custom Methods

· 약 4분

기본적으로 사용하는 기능 외에 custom method 를 추가해서 validation 을 해보자.

목차

  1. 사업자등록번호
  2. 법인등록번호
  3. 바이트 제한
  4. 아이디 체크 (alphanumeric, 숫자 첫글자 불가능)
  5. 비밀번호 체크 (alpha && (number || special char))
  6. datetime (YYYY-MM-DD HH:mm:ss)
  7. date (YYYY-MM-DD)
  8. Kakaotalk Yellow ID
  9. alphanumeric (hyphen, underscore, space 포함)
  10. phone (hyphen 포함)
  11. mobile (hyphen 포함)

소스

(function ($) {
$.validator.addMethod(
"biznum",
function (bizID, element) {
const checkID = [1, 3, 7, 1, 3, 7, 1, 3, 5, 1];
let tmpBizID,
i,
chkSum = 0,
c2,
remander;
bizID = bizID.replace(/-/gi, "");

for (i = 0; i <= 7; i++) {
chkSum += checkID[i] * bizID.charAt(i);
}
c2 = "0" + checkID[8] * bizID.charAt(8);
c2 = c2.substring(c2.length - 2, c2.length);
chkSum += Math.floor(c2.charAt(0)) + Math.floor(c2.charAt(1));
remander = (10 - (chkSum % 10)) % 10;
return this.optional(element) || Math.floor(bizID.charAt(9)) === remander;
},
"사업자등록번호 형식에 맞지 않습니다",
);

$.validator.addMethod(
"corpnum",
function (corpID, element) {
let result = true;
if (corpID.length === 13) {
const arr_regno = corpID.split("");
const arr_wt = [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2];
let iSum_regno = 0;
let iCheck_digit = 0;

for (i = 0; i < 12; i++) {
iSum_regno += Number(arr_regno[i]) * Number(arr_wt[i]);
}

iCheck_digit = 10 - (iSum_regno % 10);
iCheck_digit = iCheck_digit % 10;

if (iCheck_digit !== arr_regno[12]) {
result = false;
}
} else {
result = false;
}

return this.optional(element) || result;
},
"법인등록번호 형식에 맞지 않습니다",
);

$.validator.addMethod(
"byte",
function (str, element, param) {
let byte = 0;
let result = true;

for (let i = 0, len = text.length; i < len; i++) {
if (escape(text.charAt(i)).length > 4) {
byte = byte + 2;
} else {
byte = byte + 1;
}
}

if (byte > param) {
result = false;
}

return this.optional(element) || result;
},
"최대 Byte 값을 넘었습니다",
);

// id 체크 (alphanumeric, _- 가능, 숫자가 처음에 올수 없음)
$.validator.addMethod(
"user",
function (id, element) {
return (
this.optional(element) ||
/^([a-zA-Z])[a-zA-Z_-]*[\w_-]*[\S]$|^([a-zA-Z])[0-9_-]*[\S]$|^[a-zA-Z]*[\S]$/.test(
id,
)
);
},
"올바른 아이디 형식이 아닙니다",
);

// pw 영문 && (숫자 || 특수문자)
$.validator.addMethod(
"pass",
function (pass, element) {
return (
this.optional(element) ||
/^(?=.*[a-zA-Z])((?=.*\d)|(?=.*\W))./.test(pass)
);
},
"올바른 비밀번호 형식이 아닙니다",
);

// datetime 형식
$.validator.addMethod(
"datetime",
function (datetime, element) {
return (
this.optional(element) ||
/^\d{4}-(0[1-9]|1[0-2])-([0-2]\d|3[01]) (0\d|1\d|2[0-3]):[0-5]\d:[0-5]\d$/.test(
datetime,
)
);
},
"올바른 날짜, 시간형식이 아닙니다",
);

// date 형식
$.validator.addMethod(
"date",
function (dt, element) {
return (
this.optional(element) ||
/^\d{4}-(0[1-9]|1[0-2])-([0-2]\d|3[01])$/.test(dt)
);
},
"올바른 날짜 형식이 아닙니다",
);

// 옐로아이디 형식
$.validator.addMethod(
"yellowid",
function (yid, element) {
return this.optional(element) || /^@[\W|\w]{2,15}/.test(yid);
},
"올바른 옐로아이디가 아닙니다",
);

// alpahnumeric _ - space
$.validator.addMethod(
"alphanumeric",
function (v, element) {
return this.optional(element) || /^[a-zA-Z\d\-_\s]+$/.test(v);
},
"올바른 형식이 아닙니다",
);

// 하이픈을 포함한 전화번호
$.validator.addMethod(
"phone",
function (p, element) {
return this.optional(element) || /^\d{2,3}-\d{3,4}-\d{4}$/.test(p);
},
"올바른 전화번호 형식이 아닙니다",
);

// 하이픈을 포함한 휴대폰 번호
$.validator.addMethod(
"mobile",
function (m, element) {
return (
this.optional(element) ||
/^01([0|1|6|7|8|9]?)-(\d{3,4})-(\d{4})$/.test(m)
);
},
"올바른 휴대폰 번호 형식이 아닙니다",
);
})(jQuery);

설명

biznum, byte...와 같은 속성을 추가해 사용하면 된다. 필요한 부분만 복사해 가져가도 되고.

예제

$("form").validate({
rules: {
text_field: { byte: 80 },
date_field: { date: true },
},
messages: {
text_field: { byte: "80자 초과" },
date_field: { date: "날짜 형식 아님" },
},
});

여담

byte check 의 함수 로직이 많지만, 한글 및 특수문자를 2byte 로 정확히 체크해주는 것은 위의 함수 뿐이였다.