본문 바로가기
연습

프로그래머뇌 청킹연습/표식찾기연습 - 2

by ybs 2022. 5. 15.
반응형

 

[표식찾기 연습]

1단계 코드베이스 선택 

owasp-java-html-sanitizer 에서 HtmlSanitizer 클래스의 sanitize 메서드

  public static void sanitize(
      @Nullable String html, final Policy policy,
      HtmlStreamEventProcessor preprocessor) {
    String htmlContent = html != null ? html : "";

    HtmlStreamEventReceiver receiver = initializePolicy(policy, preprocessor);

    receiver.openDocument();

    HtmlLexer lexer = new HtmlLexer(htmlContent);
    // Use a linked list so that policies can use Iterator.remove() in an O(1)
    // way.
    LinkedList<String> attrs = Lists.newLinkedList();
    while (lexer.hasNext()) {
      HtmlToken token = lexer.next();
      switch (token.type) {
        case TEXT:
          receiver.text(
              Encoding.decodeHtml(htmlContent.substring(token.start, token.end)));
          break;
        case UNESCAPED:
          receiver.text(Encoding.stripBannedCodeunits(
              htmlContent.substring(token.start, token.end)));
          break;
        case TAGBEGIN:
          if (htmlContent.charAt(token.start + 1) == '/') {  // A close tag.
            receiver.closeTag(HtmlLexer.canonicalElementName(
                htmlContent.substring(token.start + 2, token.end)));
            while (lexer.hasNext()
                   && lexer.next().type != HtmlTokenType.TAGEND) {
              // skip tokens until we see a ">"
            }
          } else {
            attrs.clear();

            boolean attrsReadyForName = true;
            tagBody:
            while (lexer.hasNext()) {
              HtmlToken tagBodyToken = lexer.next();
              switch (tagBodyToken.type) {
                ...

 

2단계 코드 파악: 선택한 메서드나 함수를 파악하고 코드가 하는 일을 요약

먼저 sanitize 메서드에 필요한 3개 파라미터부터 살펴보자.

@Nullable String html,
final Policy policy,
HtmlStreamEventProcessor preprocessor

 

1. sanitize 대상이 되는 html 문자열

2. sanitize 정책

3. HtmlStreamEventProcessor 는 네이밍으로 'html 문자열 스트림 이벤트를 처리하는 프로세스' 를 담당하는것으로 유추해볼 수 있다. 하지만 이것만으로는 정확히 무슨일을 하는지 알기 어렵다.

 

다음 코드를 보자.

String htmlContent = html != null ? html : "";

HtmlStreamEventReceiver receiver = initializePolicy(policy, preprocessor);

receiver.openDocument();

 

htmlContent 로 전달은 방어로직으로 이해했다. 다음으로 policy 와 preprocessor를 갖고 초기화 작업을 한다. 그런데 리턴 타입이 HtmlStreamEventReceiver 다. 결국 Policy, HtmlStreamEventProcessor, HtmlStreamEventReceiver 이 3개의 타입에 대해 이해가 필요하다.

 

위 다이어그램을 보면 모두 HtmlStreamEventReceiver 와 연결되는것을 알 수 있다. 결론적으로 initializePolicy를 통해 얻은 receiver 변수는 TagBalancingHtmlStreamEventReceiver 타입이고 그 안에는 ElementAndAttributePolicyBasedSanitizerPolicy 를 갖고 있다. HtmlStreamEventProcessor 에서는 현재 전달받은 HtmlStreamEventReceiver 타입을 그대로 리턴하기 때문에 TagBalancingHtmlStreamEventReceiver 를 wrapping 했다고 이해하면 된다.

HtmlStreamEventReceiver receiver = initializePolicy(policy, preprocessor);

 

실질적인 openDocument 는 ElementAndAttributePolicyBasedSanitizerPolicy 의 openDocument 이고, 내부적으로 sanitize 결과를 담는 out(HtmlStreamRenderer 타입) 의 openDocument 가 수행된다.

receiver.openDocument();

 

다음 아래 코드를 보자. htmlContent 를 갖고 HtmlLexer 객체를 만든다. HtmlLexer 를 통해 htmlContent 중 의미있는 단위로 짤라서 HtmlToken 객체로 만든다(각 단위를 token이라 한다). 그리고 token type에 따라 역할들이 분기되어 있다. htmlContent를 어떻게 HtmlToken 단위로 만드는지는 내용이 방대하므로 따로 다루겠다.

    HtmlLexer lexer = new HtmlLexer(htmlContent);

    // Use a linked list so that policies can use Iterator.remove() in an O(1)
    // way.
    LinkedList<String> attrs = Lists.newLinkedList();
    while (lexer.hasNext()) {
      HtmlToken token = lexer.next();
      switch (token.type) {
        case TEXT:
          ...
        case UNESCAPED:
          ...
        case TAGBEGIN:
          if (htmlContent.charAt(token.start + 1) == '/') {  // A close tag.
            ...
          } else {
                ...
          }
      }
    }

 

각 token type 마다 최종적으로는 receiver.text, receiver.closeTag, receiver.openTag 에 담는다. 이중에서 가장 핵심인 코드는 TAGBEGIN인데 이것도 내용이 길어서 중간을 짤랐다. 이후 부분은 다음 분석때 다루겠다.

      switch (token.type) {
        case TEXT:
          receiver.text(
              Encoding.decodeHtml(htmlContent.substring(token.start, token.end)));
          break;
        case UNESCAPED:
          receiver.text(Encoding.stripBannedCodeunits(
              htmlContent.substring(token.start, token.end)));
          break;
        case TAGBEGIN:
          if (htmlContent.charAt(token.start + 1) == '/') {  // A close tag.
            receiver.closeTag(HtmlLexer.canonicalElementName(
                htmlContent.substring(token.start + 2, token.end)));
            while (lexer.hasNext()
                   && lexer.next().type != HtmlTokenType.TAGEND) {
              // skip tokens until we see a ">"
            }
          } else {
            attrs.clear();

            boolean attrsReadyForName = true;
            tagBody:
            while (lexer.hasNext()) {
              HtmlToken tagBodyToken = lexer.next();
              switch (tagBodyToken.type) {
                ...

 

3단계 사용하는 표식의 적극적 확인

설명 : 읽는 도중 '아, 그렇구나'라는 생각이 들면서 그 코드의 의미를 좀 더 이해하게 되면 잠시 멈추고 왜 그렇게 생각했는지를 적어보라. 주석문, 변수명, 메서드명, 임시 저장값 등 어느 것이든 표식이 될 수 있다.

 

1. initializePolicy 작업을 통해 receiver 변수에 모든것이 계층적으로 셋팅되는것을 이해했다. 위 밴다이어그램을 통해 알 수 있듯이 HtmlStreamEventReceiver 타입으로 엮여있다.

 

2. Lexer 는 laxical analysis(어휘분석)을 하는 역할이다. 즉 HtmlLexer 는 html 문자열을 분석해서 token 단위로 분리시켜 sanitize 함을 알 수 있다.

 

3. attrs 변수는 컨테이너 객체로 linked list를 선택했는데, Iterator.remove() 를 O(1) 복잡도로 실행시키기 위함이라고 주석에 설명되어있다. 하지만 attrs 를 어떤 목적으로 사용하는지에 대해 아직 구체적으로 파악되지 않아 완전히 이해되지 않았다.

 

4단계 회고

- 어떤 표식을 찾았는가?

1. receiver

2. initializePolicy

3. HtmlLexer

4. HtmlToken

 

- 찾은 표식들은 코드의 요소인가, 아니면 사람의 언어로 된 정보인가?

코드의 요소다.

 

- 그 표식들은 무엇에 관해 알려주고 있는가?

1. receiver : wrapping 되어 있지만 결국 sanitize 된 결과를 담는 out 변수에 넣는 역할을 수행한다.

2. initializePolicy : receiver 구조를 만드는 작업을 수행한다. 계층적으로 다양한 객체들이 담겨있다.

3. HtmlLexer : htmlContent 를 의미있는 단위로 자르는 역할이다.

4. HtmlToken : HtmlLexer 가 token 단위로 잘르면 HtmlToken 자료구조로 담는다.

 

- 그 표식들은 코드의 도메인에 대한 지식을 나타내는가?

HtmlLexer, HtmlToken 은 도메인에 대한 지식이다.

 

- 그 표식들은 코드의 기능에 대한 지식을 나타내는가?

receiver, initializePolicy는 기능에 대한 지식이다.

 

[청킹연습]

1단계 코드 선정

  public static void sanitize(
      @Nullable String html, final Policy policy,
      HtmlStreamEventProcessor preprocessor) {
    String htmlContent = html != null ? html : "";

    HtmlStreamEventReceiver receiver = initializePolicy(policy, preprocessor);

    receiver.openDocument();

    HtmlLexer lexer = new HtmlLexer(htmlContent);
    // Use a linked list so that policies can use Iterator.remove() in an O(1)
    // way.
    LinkedList<String> attrs = Lists.newLinkedList();
    while (lexer.hasNext()) {
      HtmlToken token = lexer.next();
      switch (token.type) {
        case TEXT:
          receiver.text(
              Encoding.decodeHtml(htmlContent.substring(token.start, token.end)));
          break;
        case UNESCAPED:
          receiver.text(Encoding.stripBannedCodeunits(
              htmlContent.substring(token.start, token.end)));
          break;
        case TAGBEGIN:
          if (htmlContent.charAt(token.start + 1) == '/') {  // A close tag.
            receiver.closeTag(HtmlLexer.canonicalName(
                htmlContent.substring(token.start + 2, token.end)));
            while (lexer.hasNext()
                   && lexer.next().type != HtmlTokenType.TAGEND) {
              // skip tokens until we see a ">"
            }
          } else {
            attrs.clear();

            boolean attrsReadyForName = true;
            tagBody:
            while (lexer.hasNext()) {
              HtmlToken tagBodyToken = lexer.next();
              switch (tagBodyToken.type) {

 

2단계 코드 파악

설명: 최대 2분을 넘지 않도록 타이머를 설정하고 코드 파악. 시간이 다 되면 코드는 보지 않는다.

3단계 코드 재현

설명: 기억을 되살려 새롭게 코드를 다시 작성

  public static void sanitize(
    @Nullable String html,
    HtmlSanitizer.Policy policy,
    HtmlStreamEventProcessor preProcessor
  ) {
    String htmlContent = html != null ? html : "";
    HtmlStreamEventReceiver receiver = initializePolicy(policy, preProcessor);
    receiver.openDocument();
    LinkedList<String> attrs = Lists.newLinkedList();
    HtmlLexer lexer = new HtmlLexer(htmlContent);
    while (lexer.hasNext()) {
      HtmlToken token = lexer.next();
      switch (token.type) {
        case TEXT:
          // token substring
          receiver.text();
          break;
        case UNESCAPED:
          // token substring
          receiver.text();
          break;
        case TAGBEGIN:
          if () { // close tag </
            // token substring 
          } else {
            while(lexer.hasNext()) {
              HtmlToken tagBodyToken = lexer.next();
              // receiver.openTag
            }
          }
          break;
      }
    }
  }

  private static HtmlStreamEventReceiver initializePolicy(HtmlSanitizer.Policy policy, HtmlStreamEventProcessor preProcessor) {
    TagBalancingHtmlStreamEventReceiver sink = new TagBalancingHtmlStreamEventReceiver(policy);
    sink.setNestingLimit(256);
    return preProcessor.wrap(sink);
  }

4단계 회고

- 어느 부분을 쉽게 기억했는가?

1. sanitize signature

2. initializePolicy

3. HtmlLexer 그리고 while - switch 전체 구조.

 

- 부분적으로 기억한 코드가 있는가?

switch case 별로 작업하는 코드들

 

- 전체를 다 기억하지 못한 코드가 있는가?

tagBodyToken 부분

 

- 기억하지 못한 라인들이 있다면 그 이유가 무엇일까?

시간부족. 

 

- 기억하지 못한 라인에 본인이 익숙하지 않은 프로그래밍 개념이 들어 있지는 않는가?

없다.

 

- 기억하지 못한 라인에 본인이 익숙하지 않은 도메인 지식이 있지는 않는가?

receiver.text 나 receiver.closeTag 에 들어갈 값들에 대해 추가적인 작업이 필요한데 Encoding.decodeHtml,  Encoding.stripBannedCodeunits 이 왜 필요한지에 대해 아직 도메인 지식이 부족한다.

 

그리고 HtmlToken 이 만들어지는 구체적인 프로세스에 대한 도메인 지식이 부족해서 htmlContent.substring 로직들을 잘 기억해내지 못했다.

반응형