Rev. 2.73

웹사이트의 외부링크에 미리보기 이미지를 제공하는 서비스인 Snap.com은 광고모델과 접목시키면서부터 더이상 공짜로 프리뷰 이미지를 제공하지 않게 되었다. Snap.com은 프리뷰 이미지를 제공하는 서비스들 중에도 메이저 급에 해당하는 서비스로서 대표적으로는 Alexa Site Thumbnail 등이 있다. 이전의 서비스 방침은 회원 가입 후 사용자 키를 인증받아 요청하는 방식으로 비교적 간단하게 사용할 수 있었지만, 최근 시간 값을 가진 토큰(Token)을 추가하여 사실상 영원히 지속되는 프리뷰 이미지로의 접근을 막아 버린 것이다. 아래는 Snap.com의 주소 요청 스팩의 변경 내역이다.

/* 최초 이미지 요청 주소 */
shots.snap.com/preview/?url=http%3A%2F%2Ffirejune.com%2F&key=23fd8eb03b5aaa509e763e972aa05f&size=small

/* 토큰이 적용된 이미지 주소 */
shots.snap.com/preview/?url=http%3A%2F%2Ffirejune.com%2F&key=23fd8eb03b5aaa509e763e972aa05f&tok=000347faa37c97925f187cfaeedc5b7e&size=small

/* 토큰을 암호화한 이미지 주소 */
shots.snap.com/preview/?url=http%3A%2F%2Ffirejune.com%2F&key=23fd8eb03b5aaa509e763e972aa05f&src=&cp=&sb=1&v=3.24&size=small&lang=ko-kr&search_type=spasense&origin=shots_bubble&act=both_link&po=0&rp=null&tok=000347faa37c97925f187cfaeedc5b7e016644f19c&has_img=0&ol=0&ex=0&ad=unknown&ip=000.110.000.202&ua=Mozilla%2F5.0+%28Windows%3B+U%3B+Windows+NT+6.0%3B+ko%3B+rv%3A1.8.1.13%29+Gecko%2F20080311+Firefox%2F2.0.0.13&vid=b6fd6f4617146baf532db247af9176c5&nl=0&referrer=http%3A%2F%2Ffirejune.com%2F%23pl%3D1309&svc=&view_id=b6fd6f4617146baf532db247af9176c5&goto=%25URL(%3C)%20t%C3%99&direct=1&sc=0

토큰은 Snap.com에서 제공하는 외부 자바스크립트 파일에 변수로 그때 그때 생성되어 접근하도록 되어있다. 이 토큰은 약 하루정도의 시효를 가지고 있으며, 시효가 지난 토큰 값을 가진 이미지는 더미 이미지로 리다이렉트 된다. 이 블로그 역시 Snap.com의 프리뷰 이미지 서비스를 사용하고 있는데, Snap.com이 자체적으로 지원하는 프리뷰 위젯인 Snap Preview Anywher는 워낙에 덩치가 크고 불편하여 자체적으로 만들어 사용하고 있는 실정이다.

일단, 토큰 값을 알아내기 위해 서버단에서 Snap.com의 자바스크립트 파일을 분석하여 값을 추출하는 방식으로 다시금 이미지를 출력하는데 성공하였으나, 요 며칠전 Snap.com은 또 다시 스팩을 변경하여 프리뷰 이미지가 비정상적으로 출력되는 것을 확인하고 뚜껑(?)이 살짝 열린 상태에서 다시금 분석해 보았다. 어설프게도 자바스크립트를 이용하여 토큰을 해시(암호)코드로 다시 만들어 넘기는 방식이다. 그래서 아래와 같은 Prototype기반 클래스를 만들어 버렸다.

/* Snap Preview Anywhere Hack */
var SnapPreviewHack = Class.create({
  initialize: function() {
    this.key = '23fd8eb03b5aaa509e763e972aa05f'; /* user key */
    this.cfg = Cookie.get('SnapCfg') || null;

    if (!this.cfg) {
      this.getSnapCfg();
    } else {
      var arr = this.cfg.split('#-#');
      if (!arr[1] || arr[1] < new Date().getTime() - 86400000) {
        this.getSnapCfg();
      } else {
        try {
          this.cfg = arr[0].evalJSON();
        } catch(e) {
          this.getSnapCfg();
        }
      }
    }
  },
  getSnapCfg: function() {
    new Ajax.Request('/ajax/snapcfg.php', {
      onSuccess: function(req) {
        Cookie.set('SnapCfg', req.responseText + '#-#' + new Date().getTime());
        this.cfg = req.responseJSON;
      }.bind(this)
    });
  },
  getSnapSrc: function(event, href) {
    var vid = this.rand_hash(this.cfg.rnd + (event.clientX || 0) + (event.clientY || 0)),
    ua = navigator.userAgent.split(' ').join('+'),
    encode = function(uri) { return encodeURIComponent(uri); };
    /* return fake src */
    return 'http://shots.snap.com/preview/?url=' + encode(href) +
           '&key=' + this.key + '&src=' + encode((this.cfg.source || '')) + '&cp=' + (this.cfg.campaign || '') + 
           '&sb=' + (this.cfg.has_marea || '1') + '&v=' + this.cfg.version + '&size=' + this.cfg.size + '&lang=' + this.cfg.lang + 
           '&search_type=' + this.cfg.search_type + '&origin=shots_bubble&act=both_link&po=' + (this.cfg.preview_only ? "1": "0") + 
           '&rp=' + encode(this.cfg.redirect_param) + '&tok=' + this.tok(href) + '&has_img=0&ol=0&ex=0&ad=unknown' +
           '&ip=' + this.cfg.client_ip + '&ua=' + encode(ua) + '&vid=' + vid + '&nl=0&referrer=' + encode(document.location.href) +




           '&svc=&view_id=' + vid + '&goto=' + encode(this.cfg.GoToURL) + '&direct=1&sc=0';
  },
  /* piece of snap preview core  */
  hash32: function(D) {
    var C = 5003;
    D = D.toString();
    for (var B = 0; B < D.length; ++B) {
      C += D.charCodeAt(B);
      C += (C << 10);
      C ^= (C >> 6);
    }
    C += (C << 3);
    C ^= (C >> 11);
    C += (C << 15);
    C = Math.abs(C).toString(16);
    while (C.length < 8) {
      C = "0" + C;
    }
    return C;
  },
  rand_hash: function(B) {
    var C, D = ["", "", "", ""];
    B = B.toString() + Math.random() + document.location.href + new Date().getTime() + window.navigator.userAgent;
    for (C = 0; C < B.length; C++) {
      D[C % 4] += B.charAt(C);
    }
    return this.hash32(D[0]) + this.hash32(D[1]) + this.hash32(D[2]) + this.hash32(D[3]);
  },
  tok: function(B) {
    return this.cfg.tkn + "01" + this.hash32(this.cfg.tkn + "" + B);
  }
});

이미지 주소에 붙는 파라메터 값이 엄청나게 많은데 모두가 필수요소는 아니다. key, tok, v, 그리고 vid 정도만 넘겨줘도 이미지를 호출 할 수 있었다. 토큰은 하루정도의 시효가 있기 때문에 매번 서버로부터 호출하는 것이 불필요해서 쿠키에 JSON을 저장하고 비교하는 로직이 포함되어 있다. 위 클래스는 다음처럼 사용된다.

  initialize: function() {
    this.sph = new SnapPreviewHack();
    ...
  },
  ...
  /* event = mouse event, href = domain */
  createShot: function(event, href) {
    return new Element('img', {
      src = this.sph.getSnapSrc(event, href);
    }); 
  },
  ...

서버로부터 분석한 결과를 아래와 같이 JSON으로 수신 받아 처리한다.(변수는 실제 값과 다름)

{
  version: '3.24',
  size: 'small',
  lang: 'ko-kr',
  search_type: 'spasense',
  GoToURL: '%URL(으)로 이동',
  preview_only: false ,
  redirect_param: null,
  tkn: '000347fad03d5d5ba14bee5a546b9b1f',
  rnd: 'f9bb00debbf6b92e865ec64cbe807085',
  client_ip: '000.110.000.202'
}

그러고 보니, 시덥잖은 일로 소중한 주말 밤을 하얗게 불태웠다.

추가 2008-05-19: tok의 알고리듬이 아래와 같이 변경되었다.

//변경전:
tok: function(B) {
  return this.cfg.tkn + "01" + this.hash32(this.cfg.tkn + "" + B);
}
//변경후:
tok: function(B) {
  return this.cfg.tkn + "02" + this.hash32(this.cfg.tkn + "15c1c0ae" + B); 
}

Comments

Got something to add? You can just leave a comment.

Your Reaction Time!

captcha

avatar