|
7 | 7 | #include <wchar.h>
|
8 | 8 | #include <userenv.h>
|
9 | 9 | #include <lm.h>
|
| 10 | +#include <winhttp.h> |
10 | 11 |
|
11 | 12 | #define PERL_NO_GET_CONTEXT
|
12 | 13 | #include "EXTERN.h"
|
@@ -1682,6 +1683,248 @@ XS(w32_IsDeveloperModeEnabled)
|
1682 | 1683 | XSRETURN_NO;
|
1683 | 1684 | }
|
1684 | 1685 |
|
| 1686 | + |
| 1687 | +XS(w32_HttpGetFile) |
| 1688 | +{ |
| 1689 | + dXSARGS; |
| 1690 | + WCHAR *url = NULL, *file = NULL, *hostName = NULL, *urlPath = NULL; |
| 1691 | + DWORD dwSize = 0; |
| 1692 | + DWORD dwDownloaded = 0; |
| 1693 | + DWORD dwBytesWritten = 0; |
| 1694 | + LPSTR pszOutBuffer; |
| 1695 | + BOOL bResults = FALSE; |
| 1696 | + HINTERNET hSession = NULL, |
| 1697 | + hConnect = NULL, |
| 1698 | + hRequest = NULL; |
| 1699 | + HANDLE hOut = NULL; |
| 1700 | + BOOL bParsed = FALSE, |
| 1701 | + bAborted = FALSE, |
| 1702 | + bFileError = FALSE; |
| 1703 | + DWORD error = 0; |
| 1704 | + URL_COMPONENTS urlComp; |
| 1705 | + LPCWSTR acceptTypes[] = { L"*/*", NULL }; |
| 1706 | + WINHTTP_AUTOPROXY_OPTIONS AutoProxyOptions; |
| 1707 | + WINHTTP_PROXY_INFO ProxyInfo; |
| 1708 | + DWORD cbProxyInfoSize = sizeof(ProxyInfo); |
| 1709 | + |
| 1710 | + if (items != 2) |
| 1711 | + croak("usage: Win32::HttpGetFile($url, $file)"); |
| 1712 | + |
| 1713 | + url = sv_to_wstr(aTHX_ ST(0)); |
| 1714 | + file = sv_to_wstr(aTHX_ ST(1)); |
| 1715 | + |
| 1716 | + /* Initialize the URL_COMPONENTS structure, setting the required |
| 1717 | + * component lengths to non-zero so that they get populated. |
| 1718 | + */ |
| 1719 | + ZeroMemory(&urlComp, sizeof(urlComp)); |
| 1720 | + urlComp.dwStructSize = sizeof(urlComp); |
| 1721 | + urlComp.dwSchemeLength = (DWORD)-1; |
| 1722 | + urlComp.dwHostNameLength = (DWORD)-1; |
| 1723 | + urlComp.dwUrlPathLength = (DWORD)-1; |
| 1724 | + urlComp.dwExtraInfoLength = (DWORD)-1; |
| 1725 | + |
| 1726 | + /* Parse the URL. */ |
| 1727 | + bParsed = WinHttpCrackUrl(url, (DWORD)wcslen(url), 0, &urlComp); |
| 1728 | + |
| 1729 | + /* Only support http and htts, not ftp, gopher, etc. */ |
| 1730 | + if (bParsed |
| 1731 | + && !(urlComp.nScheme == INTERNET_SCHEME_HTTPS |
| 1732 | + || urlComp.nScheme == INTERNET_SCHEME_HTTP)) { |
| 1733 | + SetLastError(12006); /* not a recognized protocol */ |
| 1734 | + bParsed = FALSE; |
| 1735 | + } |
| 1736 | + |
| 1737 | + if (bParsed) { |
| 1738 | + New(0, hostName, urlComp.dwHostNameLength + 1, WCHAR); |
| 1739 | + wcsncpy(hostName, urlComp.lpszHostName, urlComp.dwHostNameLength); |
| 1740 | + hostName[urlComp.dwHostNameLength] = 0; |
| 1741 | + |
| 1742 | + New(0, urlPath, urlComp.dwUrlPathLength + urlComp.dwExtraInfoLength + 1, WCHAR); |
| 1743 | + wcsncpy(urlPath, urlComp.lpszUrlPath, urlComp.dwUrlPathLength + urlComp.dwExtraInfoLength); |
| 1744 | + urlPath[urlComp.dwUrlPathLength + urlComp.dwExtraInfoLength] = 0; |
| 1745 | + |
| 1746 | + /* Use WinHttpOpen to obtain a session handle. */ |
| 1747 | + hSession = WinHttpOpen(L"Perl", |
| 1748 | + WINHTTP_ACCESS_TYPE_NO_PROXY, |
| 1749 | + WINHTTP_NO_PROXY_NAME, |
| 1750 | + WINHTTP_NO_PROXY_BYPASS, |
| 1751 | + 0); |
| 1752 | + } |
| 1753 | + |
| 1754 | + /* Specify an HTTP server. */ |
| 1755 | + if (hSession) |
| 1756 | + hConnect = WinHttpConnect(hSession, |
| 1757 | + hostName, |
| 1758 | + urlComp.nPort, |
| 1759 | + 0); |
| 1760 | + |
| 1761 | + /* Create an HTTP request handle. */ |
| 1762 | + if (hConnect) |
| 1763 | + hRequest = WinHttpOpenRequest(hConnect, |
| 1764 | + L"GET", |
| 1765 | + urlPath, |
| 1766 | + NULL, |
| 1767 | + WINHTTP_NO_REFERER, |
| 1768 | + acceptTypes, |
| 1769 | + urlComp.nScheme == INTERNET_SCHEME_HTTPS |
| 1770 | + ? WINHTTP_FLAG_SECURE |
| 1771 | + : 0); |
| 1772 | + |
| 1773 | + /* Call WinHttpGetProxyForUrl with our target URL. If auto-proxy succeeds, |
| 1774 | + * then set the proxy info on the request handle. If auto-proxy fails, |
| 1775 | + * ignore the error and attempt to send the HTTP request directly to the |
| 1776 | + * target server (using the default WINHTTP_ACCESS_TYPE_NO_PROXY |
| 1777 | + * configuration, which the request handle will inherit from the session). |
| 1778 | + */ |
| 1779 | + if (hRequest) { |
| 1780 | + ZeroMemory(&AutoProxyOptions, sizeof(AutoProxyOptions)); |
| 1781 | + ZeroMemory(&ProxyInfo, sizeof(ProxyInfo)); |
| 1782 | + AutoProxyOptions.dwFlags = WINHTTP_AUTOPROXY_AUTO_DETECT; |
| 1783 | + AutoProxyOptions.dwAutoDetectFlags = |
| 1784 | + WINHTTP_AUTO_DETECT_TYPE_DHCP | |
| 1785 | + WINHTTP_AUTO_DETECT_TYPE_DNS_A; |
| 1786 | + AutoProxyOptions.fAutoLogonIfChallenged = TRUE; |
| 1787 | + |
| 1788 | + if(WinHttpGetProxyForUrl(hSession, |
| 1789 | + url, |
| 1790 | + &AutoProxyOptions, |
| 1791 | + &ProxyInfo)) { |
| 1792 | + if(!WinHttpSetOption(hRequest, |
| 1793 | + WINHTTP_OPTION_PROXY, |
| 1794 | + &ProxyInfo, |
| 1795 | + cbProxyInfoSize)) { |
| 1796 | + bAborted = TRUE; |
| 1797 | + Perl_warn(aTHX_ "Win32::HttpGetFile: setting proxy options failed"); |
| 1798 | + } |
| 1799 | + Safefree(ProxyInfo.lpszProxy); |
| 1800 | + Safefree(ProxyInfo.lpszProxyBypass); |
| 1801 | + } |
| 1802 | + } |
| 1803 | + |
| 1804 | + /* Send a request. */ |
| 1805 | + if (hRequest && !bAborted) |
| 1806 | + bResults = WinHttpSendRequest(hRequest, |
| 1807 | + WINHTTP_NO_ADDITIONAL_HEADERS, |
| 1808 | + 0, |
| 1809 | + WINHTTP_NO_REQUEST_DATA, |
| 1810 | + 0, |
| 1811 | + 0, |
| 1812 | + 0); |
| 1813 | + |
| 1814 | + /* End the request. */ |
| 1815 | + if (bResults) |
| 1816 | + bResults = WinHttpReceiveResponse(hRequest, NULL); |
| 1817 | + |
| 1818 | + /* Create output file for download. */ |
| 1819 | + if (bResults) { |
| 1820 | + hOut = CreateFileW(file, |
| 1821 | + GENERIC_WRITE, |
| 1822 | + FILE_SHARE_READ | FILE_SHARE_WRITE, |
| 1823 | + NULL, |
| 1824 | + CREATE_ALWAYS, |
| 1825 | + FILE_ATTRIBUTE_NORMAL, |
| 1826 | + NULL); |
| 1827 | + |
| 1828 | + if (!hOut || hOut == INVALID_HANDLE_VALUE) |
| 1829 | + bFileError = TRUE; |
| 1830 | + } |
| 1831 | + |
| 1832 | + /* Keep checking for data until there is nothing left. */ |
| 1833 | + if (!bFileError && bResults) { |
| 1834 | + do { |
| 1835 | + /* Check for available data. */ |
| 1836 | + dwSize = 0; |
| 1837 | + if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) { |
| 1838 | + bAborted = TRUE; |
| 1839 | + break; |
| 1840 | + } |
| 1841 | + |
| 1842 | + /* No more available data. */ |
| 1843 | + if (!dwSize) |
| 1844 | + break; |
| 1845 | + |
| 1846 | + /* Allocate space for the buffer. */ |
| 1847 | + New(0, pszOutBuffer, dwSize + 1, char); |
| 1848 | + if (!pszOutBuffer) { |
| 1849 | + bAborted = TRUE; |
| 1850 | + break; |
| 1851 | + } |
| 1852 | + |
| 1853 | + /* Read the Data. */ |
| 1854 | + ZeroMemory(pszOutBuffer, dwSize+1); |
| 1855 | + |
| 1856 | + if (!WinHttpReadData(hRequest, |
| 1857 | + (LPVOID)pszOutBuffer, |
| 1858 | + dwSize, |
| 1859 | + &dwDownloaded)) { |
| 1860 | + bAborted = TRUE; |
| 1861 | + Safefree(pszOutBuffer); |
| 1862 | + break; |
| 1863 | + } |
| 1864 | + |
| 1865 | + /* Write what we just read to the output file */ |
| 1866 | + if (!WriteFile(hOut, |
| 1867 | + pszOutBuffer, |
| 1868 | + dwDownloaded, |
| 1869 | + &dwBytesWritten, |
| 1870 | + NULL)) { |
| 1871 | + bAborted = TRUE; |
| 1872 | + bFileError = TRUE; |
| 1873 | + Safefree(pszOutBuffer); |
| 1874 | + break; |
| 1875 | + } |
| 1876 | + |
| 1877 | + Safefree(pszOutBuffer); |
| 1878 | + |
| 1879 | + /* This condition would only be reached if WinHttpQueryDataAvailable |
| 1880 | + * said there are more data to read but WinHttpReadData didn't get any. |
| 1881 | + */ |
| 1882 | + if (!dwDownloaded) |
| 1883 | + break; |
| 1884 | + } |
| 1885 | + while (dwSize > 0); |
| 1886 | + } |
| 1887 | + else { |
| 1888 | + bAborted = TRUE; |
| 1889 | + } |
| 1890 | + |
| 1891 | + /* Clean-up may lose this. */ |
| 1892 | + if (bAborted) |
| 1893 | + error = GetLastError(); |
| 1894 | + |
| 1895 | + /* Close any open handles. */ |
| 1896 | + if (hOut) CloseHandle(hOut); |
| 1897 | + if (hRequest) WinHttpCloseHandle(hRequest); |
| 1898 | + if (hConnect) WinHttpCloseHandle(hConnect); |
| 1899 | + if (hSession) WinHttpCloseHandle(hSession); |
| 1900 | + |
| 1901 | + Safefree(url); |
| 1902 | + Safefree(file); |
| 1903 | + Safefree(hostName); |
| 1904 | + Safefree(urlPath); |
| 1905 | + |
| 1906 | + if (bAborted) { |
| 1907 | + char msgbuf[ONE_K_BUFSIZE]; |
| 1908 | + DWORD msgFlags = bFileError |
| 1909 | + ? FORMAT_MESSAGE_FROM_SYSTEM |
| 1910 | + : FORMAT_MESSAGE_FROM_HMODULE; |
| 1911 | + |
| 1912 | + if (FormatMessageA(msgFlags, |
| 1913 | + GetModuleHandleA("winhttp.dll"), |
| 1914 | + error, |
| 1915 | + 0, |
| 1916 | + msgbuf, |
| 1917 | + sizeof(msgbuf) - 1, |
| 1918 | + NULL)) { |
| 1919 | + Perl_warn(aTHX_ "Error %lu in Win32::HttpGetFile: %s", error, msgbuf); |
| 1920 | + } |
| 1921 | + SetLastError(error); |
| 1922 | + XSRETURN_NO; |
| 1923 | + } |
| 1924 | + |
| 1925 | + XSRETURN_YES; |
| 1926 | +} |
| 1927 | + |
1685 | 1928 | MODULE = Win32 PACKAGE = Win32
|
1686 | 1929 |
|
1687 | 1930 | PROTOTYPES: DISABLE
|
@@ -1756,5 +1999,6 @@ BOOT:
|
1756 | 1999 | #ifdef __CYGWIN__
|
1757 | 2000 | newXS("Win32::SetChildShowWindow", w32_SetChildShowWindow, file);
|
1758 | 2001 | #endif
|
| 2002 | + newXS("Win32::HttpGetFile", w32_HttpGetFile, file); |
1759 | 2003 | XSRETURN_YES;
|
1760 | 2004 | }
|
0 commit comments