본문 바로가기

Applications

Content-Disposition in Firefox

파이어폭스에서 Content-Disposition의 동작이 크롬이나 인터넷익스플러어랑 다른 것을 확인했다.

어떻게 구현되었는지 궁금해서 소스코드를 체크아웃 해서 확인해보았다.


1. Tortoise Hg 설치(hg 클라이언트 명령 도구를 위함)

2. 코드 체크아웃

 > hg clone https://hg.mozilla.org/mozilla-central/ firefox


Content-Disposition 구현 코드


시작은 ExternalHelperAppParent 클래스의 Init메서드부터이다.

mContentDispositionHeader.IsEmpty() 메서드에 의해 Content-Disposition 헤더가 비어있지 않으면 NS_GetFilenameFromDisposition 헬퍼함수에 의해 mContentDispositionFilename 맴버변수로 파일이름을 획득하게 된다.

void

ExternalHelperAppParent::Init(ContentParent *parent,

                              const nsCString& aMimeContentType,

                              const nsCString& aContentDispositionHeader,

                              const uint32_t& aContentDispositionHint,

                              const nsString& aContentDispositionFilename,

                              const bool& aForceSave,

                              const OptionalURIParams& aReferrer,

                              PBrowserParent* aBrowser)

{

  nsCOMPtr<nsIExternalHelperAppService> helperAppService =

    do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID);

  NS_ASSERTION(helperAppService, "No Helper App Service!");

 

  nsCOMPtr<nsIURI> referrer = DeserializeURI(aReferrer);

  if (referrer)

    SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), referrer);

 

  mContentDispositionHeader = aContentDispositionHeader;

  if (!mContentDispositionHeader.IsEmpty()) { 

    NS_GetFilenameFromDisposition(mContentDispositionFilename,

                                  mContentDispositionHeader,

                                  mURI);

    mContentDisposition =

      NS_GetContentDispositionFromHeader(mContentDispositionHeader, this);

  }

  else {

    mContentDisposition = aContentDispositionHint;

    mContentDispositionFilename = aContentDispositionFilename;

  }

 

  nsCOMPtr<nsIInterfaceRequestor> window;

  if (aBrowser) {

    TabParent* tabParent = TabParent::GetFrom(aBrowser);

    if (tabParent->GetOwnerElement())

      window = do_QueryInterface(tabParent->GetOwnerElement()->OwnerDoc()->GetWindow());

  }

 

  helperAppService->DoContent(aMimeContentType, this, window,

                              aForceSave, nullptr,

                              getter_AddRefs(mListener));

}


위치: firefox/netwerk/base/nsNetUtil.h  (network가 아닌 netwerk인줄 모르겠다. 일부러 철자를 틀리게 쓴 건가?)

함수명: NS_GetFilenameFromDisposition

헤더에 구현이 되어 있는 이유는 인라인함수이기 때문이었다.

함수 코드는 아래와 같다. (저장소 주소: 링크)

do_GetService 함수는 파이어폭스 곳곳에서 볼 수 있는데 이 함수를 알려면 먼저 XPCOM에대해 알아야 한다.

XPCOM(cross platform component object model)은 마이크로소프트의 COM과 유사한 크로스 플랫폼 컴포넌트 객체 모델이다. 자세한 것은 https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM를 참고하자.

inline nsresult

NS_GetFilenameFromDisposition(nsAString& aFilename,

                              const nsACString& aDisposition,

                              nsIURI* aURI = nullptr)

{

  aFilename.Truncate();

 

  nsresult rv;

  nsCOMPtr<nsIMIMEHeaderParam> mimehdrpar =

      do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv);

  if (NS_FAILED(rv))

    return rv;

 

  nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);

 

  nsAutoCString fallbackCharset;

  if (url)

    url->GetOriginCharset(fallbackCharset);

  // Get the value of 'filename' parameter

  rv = mimehdrpar->GetParameterHTTP(aDisposition, "filename",

                                    fallbackCharset, true, nullptr,

                                    aFilename);

 

  if (NS_FAILED(rv)) {

    aFilename.Truncate();

    return rv;

  }

 

  if (aFilename.IsEmpty())

    return NS_ERROR_NOT_AVAILABLE;

 

  return NS_OK;

}

실제 do_GetService 인라인함수는 firefox/xpcom/glue/nsServiceManagerUtils.h에 정의되어 있는 함수로 다음과 같이 오버로딩되어 있다.

inline const nsGetServiceByCID do_GetService(const nsCID& aCID)

{

  return nsGetServiceByCID(aCID);

}

 

inline const nsGetServiceByCIDWithError do_GetService(const nsCID& aCID, nsresult* aError)

{

  return nsGetServiceByCIDWithError(aCID, aError);

}

 

inline const nsGetServiceByContractID do_GetService(const char* aContractID)

{

  return nsGetServiceByContractID(aContractID);

}

 

inline const nsGetServiceByContractIDWithError do_GetService(const char* aContractID,
                                                              nsresult* aError)

{

  return nsGetServiceByContractIDWithError(aContractID, aError);

}


MIME에 대해 filename이라는 파라메터를 구해오도록 호출하는데, nsMIMEHeaderParamImpl 클래스의 메서드는 래퍼 함수인 DoGetParameter를 호출하도록 되어있다.

위치: firefox/netwerk/mime/nsMIMEHeaderParamImpl.cpp

NS_IMETHODIMP

nsMIMEHeaderParamImpl::GetParameterHTTP(const nsACString& aHeaderVal,

                                        const char *aParamName,

                                        const nsACString& aFallbackCharset,

                                        bool aTryLocaleCharset,

                                        char **aLang, nsAString& aResult)

{

  return DoGetParameter(aHeaderVal, aParamName, HTTP_FIELD_ENCODING,

                        aFallbackCharset, aTryLocaleCharset, aLang, aResult);

}


nsresult

nsMIMEHeaderParamImpl::DoGetParameter(const nsACString& aHeaderVal,

                                      const char *aParamName,

                                      ParamDecoding aDecoding,

                                      const nsACString& aFallbackCharset,

                                      bool aTryLocaleCharset,

                                      char **aLang, nsAString& aResult)

{

    aResult.Truncate();

    nsresult rv;

 

    // get parameter (decode RFC 2231/5987 when applicable, as specified by

    // aDecoding (5987 being a subset of 2231) and return charset.)

    nsXPIDLCString med;

    nsXPIDLCString charset;

    rv = DoParameterInternal(PromiseFlatCString(aHeaderVal).get(), aParamName,

                             aDecoding, getter_Copies(charset), aLang,

                             getter_Copies(med));

    if (NS_FAILED(rv))

        return rv;

 

    // convert to UTF-8 after charset conversion and RFC 2047 decoding

    // if necessary.

   

    nsAutoCString str1;

    rv = internalDecodeParameter(med, charset.get(), nullptr, false,

                                 // was aDecoding == MIME_FIELD_ENCODING

                                 // see bug 875615

                                 true,

                                 str1);

    NS_ENSURE_SUCCESS(rv, rv);

 

    if (!aFallbackCharset.IsEmpty())

    {

        nsAutoCString charset;

        EncodingUtils::FindEncodingForLabel(aFallbackCharset, charset);

        nsAutoCString str2;

        nsCOMPtr<nsIUTF8ConverterService>

          cvtUTF8(do_GetService(NS_UTF8CONVERTERSERVICE_CONTRACTID));

        if (cvtUTF8 &&

            NS_SUCCEEDED(cvtUTF8->ConvertStringToUTF8(str1,

                PromiseFlatCString(aFallbackCharset).get(), false,

                                   !charset.EqualsLiteral("UTF-8"),

                                   1, str2))) {

          CopyUTF8toUTF16(str2, aResult);

          return NS_OK;

        }

    }

 

    if (IsUTF8(str1)) {

      CopyUTF8toUTF16(str1, aResult);

      return NS_OK;

    }

 

    if (aTryLocaleCharset && !NS_IsNativeUTF8())

      return NS_CopyNativeToUnicode(str1, aResult);

 

    CopyASCIItoUTF16(str1, aResult);

    return NS_OK;

}



해당 MIME 을 테스트하는 코드는 아래와 같다.

위치: firefox/netwerk/test/unit/test_MIME_params.js  (링크)