ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 오토핫키와 Winhttp #6. multipart/form-data
    오토핫키/winhttp 2024. 7. 13. 07:17

    압살맨 오픈채팅 바로가기


    압살맨 유튜브 바로가기


    시작

    간만이죠? 일 그만두고 갓수의 생활을 즐기다가 5개월만에 다시 현생으로 복귀하면서 키보드도 다시 잡았습니다.

    이 파트는 주로 파일을 업로드할 때 사용하는 정해진 규격? 형식? 인데 첨부파일이 포함된 이메일을 송부한다던가

    특정 사이트에 이미지 PDF 등등을 업로드할 때 사용합니다. Base64 형식으로도 업로드 하는 사이트들이 있는데 Base64 는 원본보다 30% 정도 볼륨이 커지는 문제가 있어서 통신이 그만큼 느려지는 단점도 있고 리소스도 많이 먹어서 formdata 형식을 사용하는 사이트가 많이 있습니다.


    본문

    가장 먼저 우리가 업로드를 어디다가 진행하느냐를 정해야 한다.

    https://imgbb.com/

     

    무료 이미지 호스팅 / 이미지 업로드

    이미지를 업로드 하고공유해보세요. 원하는 곳 어디든 끌어놓기로 이미지를 바로 업로드해보세요.(이미지당 32 MB 가능) 다이렉트 링크, BBCode 및 HTML 미리보기등을 제공해드립니다.

    imgbb.com

    imgbb

     

    무료로 이미지를 업로드 하고 호스팅 할 수 있고 API 도 지원한다. (But, 지원하는 API 예시가 형편없어서 안 쓸 예정)

    해당 사이트에 들어가서 개발자 도구(F12) - 네트워크 탭을 누르고 새로고침

     

     

    그 이후 임의의 이미지를 업로드

     

     

    개발자 도구 안에 보면

    여기로 감

     

    json 이라고 있고 페이로드 - 소스 보기

     

    이런 데이터를 보낸 것을 알 수 있다.

    이게 바로 우리가 지금 다룰 multipart/form-data 이다.

     

    ; CreateFormData() by tmplinshi, AHK Topic: https://autohotkey.com/boards/viewtopic.php?t=7647
    ; Thanks to Coco: https://autohotkey.com/boards/viewtopic.php?p=41731#p41731
    ; Modified version by SKAN, 09/May/2016
    ; Rewritten by iseahound in September 2022
    
    CreateFormData(ByRef retData, ByRef retHeader, objParam) {
        New CreateFormData(retData, retHeader, objParam)
    }
    
    Class CreateFormData {
    
        __New(ByRef retData, ByRef retHeader, objParam) {
    
            Local CRLF := "`r`n", i, k, v, str, pvData
            ; Create a random Boundary
            Local Boundary := this.RandomBoundary()
            Local BoundaryLine := "------------------------------" . Boundary
    
            ; Create an IStream backed with movable memory.
            hData := DllCall("GlobalAlloc", "uint", 0x2, "uptr", 0, "ptr")
            DllCall("ole32\CreateStreamOnHGlobal", "ptr", hData, "int", False, "ptr*", pStream:=0, "uint")
            this.pStream := pStream
    
            ; Loop input paramters
            For k, v in objParam
            {
                If IsObject(v) {
                    For i, FileName in v
                    {
                        str := BoundaryLine . CRLF
                            . "Content-Disposition: form-data; name=""" . k . """; filename=""" . FileName . """" . CRLF
                            . "Content-Type: " . this.MimeType(FileName) . CRLF . CRLF
    
                        this.StrPutUTF8( str )
                        this.LoadFromFile( Filename )
                        this.StrPutUTF8( CRLF )
    
                    }
                } Else {
                    str := BoundaryLine . CRLF
                        . "Content-Disposition: form-data; name=""" . k """" . CRLF . CRLF
                        . v . CRLF
                    this.StrPutUTF8( str )
                }
            }
    
            this.StrPutUTF8( BoundaryLine . "--" . CRLF )
    
            this.pStream := ObjRelease(pStream) ; Should be 0.
            pData := DllCall("GlobalLock", "ptr", hData, "ptr")
            size := DllCall("GlobalSize", "ptr", pData, "uptr")
    
            ; Create a bytearray and copy data in to it.
            retData := ComObjArray( 0x11, size ) ; Create SAFEARRAY = VT_ARRAY|VT_UI1
            pvData  := NumGet( ComObjValue( retData ), 8 + A_PtrSize , "ptr" )
            DllCall( "RtlMoveMemory", "Ptr", pvData, "Ptr", pData, "Ptr", size )
    
            DllCall("GlobalUnlock", "ptr", hData)
            DllCall("GlobalFree", "Ptr", hData, "Ptr")                   ; free global memory
    
            retHeader := "multipart/form-data; boundary=----------------------------" . Boundary
        }
    
        StrPutUTF8( str ) {
            length := StrPut(str, "UTF-8") - 1 ; remove null terminator
            VarSetCapacity(utf8, length)
            StrPut(str, &utf8, length, "UTF-8")
            DllCall("shlwapi\IStream_Write", "ptr", this.pStream, "ptr", &utf8, "uint", length, "uint")
        }
    
        LoadFromFile( filepath ) {
            DllCall("shlwapi\SHCreateStreamOnFileEx"
                        ,   "wstr", filepath
                        ,   "uint", 0x0             ; STGM_READ
                        ,   "uint", 0x80            ; FILE_ATTRIBUTE_NORMAL
                        ,    "int", False            ; fCreate is ignored when STGM_CREATE is set.
                        ,    "ptr", 0               ; pstmTemplate (reserved)
                        ,   "ptr*", pFileStream:=0
                        ,   "uint")
            DllCall("shlwapi\IStream_Size", "ptr", pFileStream, "uint64*", size:=0, "uint")
            DllCall("shlwapi\IStream_Copy", "ptr", pFileStream , "ptr", this.pStream, "uint", size, "uint")
            ObjRelease(pFileStream)
        }
    
        RandomBoundary() {
            str := "0|1|2|3|4|5|6|7|8|9|a|b|c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|y|z"
            Sort, str, D| Random
            str := StrReplace(str, "|")
            Return SubStr(str, 1, 12)
        }
    
        MimeType(FileName) {
            n := FileOpen(FileName, "r").ReadUInt()
            Return (n        = 0x474E5089) ? "image/png"
                :  (n        = 0x38464947) ? "image/gif"
                :  (n&0xFFFF = 0x4D42    ) ? "image/bmp"
                :  (n&0xFFFF = 0xD8FF    ) ? "image/jpeg"
                :  (n&0xFFFF = 0x4949    ) ? "image/tiff"
                :  (n&0xFFFF = 0x4D4D    ) ? "image/tiff"
                :  "application/octet-stream"
        }
    
    }

    해당 소스의 원본은

     

    https://www.autohotkey.com/boards/viewtopic.php?f=6&t=7647

     

    CreateFormData - Create "multipart/form-data" for http post (Updated:2019-01-13) - AutoHotkey Community

    CreateFormData v1.30 (2019-01-13) Source Modified version by SKAN (doesn't require BinArr.ahk) Functions: CreateFormData(ByRef retData, ByRef retHeader, objParam) CreateFormData_WinInet(ByRef retData, ByRef retHeader, objParam) Used for VxE's HTTPRequest()

    www.autohotkey.com

    이 곳인데 원작자는 'tmplinshi' 이후 개선버전은 'SKAN' 에 의해 1차 개선

    이후 'iseahound' 에 의해 최종적인 개선버전이 나왔다.

     

    아무튼

    ------WebKitFormBoundary6RstoC3kHewqbkQF
    Content-Disposition: form-data; name="source"; filename="Range_20240710111318.png"
    Content-Type: image/png
    
    
    ------WebKitFormBoundary6RstoC3kHewqbkQF
    Content-Disposition: form-data; name="type"
    
    file
    ------WebKitFormBoundary6RstoC3kHewqbkQF
    Content-Disposition: form-data; name="action"
    
    upload
    ------WebKitFormBoundary6RstoC3kHewqbkQF
    Content-Disposition: form-data; name="timestamp"
    
    1720820484981
    ------WebKitFormBoundary6RstoC3kHewqbkQF
    Content-Disposition: form-data; name="auth_token"
    
    da8c488247626df44bb336a5255962ba8f01fcc5
    ------WebKitFormBoundary6RstoC3kHewqbkQF--

     

    여기 보면 파일이 업로드 되고

    type 밑에 file

    action 밑에 upload...가 있고

     

    마지막에 auth_token 이 있다.

     

    엥? auth_token? 이게 뭐노

    네트워크 - 문서로 가면

     

    imgbb.com 새로고침 할 때인데 여기서 응답으로 가서 Ctrl + F 를 누르고 auth_token 을 검색한다.

     

    그럼 하단에 토큰값을 부여하는 script 가 있다는 것을 알 수 있다.

     

    이제 예제를 만들어보자

     

    #Include <CreateFormData> ;아까 폼데이터 만드는 소스를 include 해야함
    #Include <JSON> ;추가로 JSON 형식의 Response 를 배열로 바꿔주는 소스 깃헙에서 구하자
    
    
    wh := ComObjCreate("WinHttp.WinHttpRequest.5.1")
    
    ;Auth_Token 값 따는 부분
    wh.Open("Get", "https://imgbb.com/")
    wh.Send()
    if(RegExMatch(wh.ResponseText, "auth_token=""(.*?)""", token))
        auth_token := token1
    else {
        Msgbox, Auth_Token Error
        ExitApp
    }
    
    ;폼데이터 생성
    Obj := {"source" : [File]
          , "type" : "file"
          , "action" : "upload"
          , "timestamp" : Unix(10000)
          , "auth_token" : auth_token}
    CreateFormData(PD, CT, Obj)
    
    ;폼데이터 Post
    wh.Open("POST", "https://imgbb.com/json")
    wh.SetRequestHeader("Accept", "*/*")
    wh.SetRequestHeader("Content-Type", CT)
    wh.SetRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
    wh.Send(PD)
    MyData := Json.Load(RegExReplace(wh.ResponseText,"\\"))
    Msgbox, % MyData.image.url
    
    
    ;UnixTimeStamp 를 만들어주는 함수
    Unix(divide) {
       Static UnixStart := 116444736000000000
       DllCall("GetSystemTimeAsFileTime", "Int64P", FileTime)
       Return (FileTime - UnixStart) // divide
    }

    이러면 이제 해당 이미지 URL 을 받을 수 있다.

     

    Obj := {"source" : [File]
          , "type" : "file"
          , "action" : "upload"
          , "timestamp" : Unix(10000)
          , "auth_token" : auth_token}

    File 은 파일명이 들어가야 한다. 예를 들면 "C:\Users\OH_YS\Documents\ASM Capture\2024-07-12\Range_20240712135109.png" 혹은 같은 위치에 있다면 " Range_20240712135109.png" 이렇게 해주면 된다.

     

    참고로 imgbb.com 는 무료일 때 용량 제한이 얼마인지 나와있지는 않지만 다른 블로거의 실험에 의하면 10GB 라고 한다.


    결론

    multipart/form-data 를 사용하는 사이트를 제어하는 것은 이제 ㅈㄴ 쉽다. ㅇㅈ?

    이거 알면 웹 제어는 거의 다 아는거라고 봐도 무방하다.

     

    아주 간혹 일반적으로 받을 수 없는 데이터를 넣어서 보내야 하는 경우도 있는데 이건 나도 몰?루

    그냥 포기하는 방법을 사용함. 그럼 이만

    댓글