/* * store.cpp */ #define STORE_DOT_CPP #include "pch.h" #include "store.hpp" #define MAX_STORES 50 #define CHUNK_SIZE 4096L static SRVTYPEINFO gtypeinfo[stError] = { {"http://", 0, 7}, // inetCopy, 7}, {"https://", 0, 8}, // inetCopy, 8}, {"", 0, 0} // fileCopy, 0} }; static HINTERNET ghint = INVALID_HANDLE_VALUE; static HWND ghwnd = 0; static UINT_PTR gopts; static char *gproxy = NULL; static char *gdstore = NULL; static BOOL gcancel = false; void disperror( DWORD err, char *file ) { if (!err || err == ERROR_REQUEST_ABORTED) return; if (err == ERROR_FILE_NOT_FOUND) dprint("%s - file not found\n", file); else dprint("%s\n %s\n", file, FormatStatus(err)); } void dumpproxyinfo( VOID ) { HINTERNET hint; INTERNET_PROXY_INFO *pi; DWORD size; BOOL rc; DWORD err; hint = (gproxy) ? ghint : NULL; size = 0; rc = InternetQueryOption(hint, INTERNET_OPTION_PROXY, NULL, &size); if (rc) { SetLastError(ERROR_INVALID_DATA); return; } if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) return; pi = (INTERNET_PROXY_INFO *)LocalAlloc(LPTR, size); if (!pi) return; ZeroMemory(pi, size); rc = InternetQueryOption(hint, INTERNET_OPTION_PROXY, pi, &size); if (rc && pi->lpszProxy && *pi->lpszProxy) dprint("Using proxy server: %s\n", pi->lpszProxy); if (pi) LocalFree(pi); } DWORD fixerror( DWORD err ) { if (err == ERROR_PATH_NOT_FOUND) return ERROR_FILE_NOT_FOUND; return err; } void setdprint( DBGPRINT fndprint ) { gdprint = fndprint; } void setproxy( char *proxy ) { gproxy = proxy; } void setdstore( char *dstore ) { gdstore = dstore; } void SetParentWindow( HWND hwnd ) { ghwnd = hwnd; } void SetStoreOptions( UINT_PTR opts ) { gopts = opts; } DWORD GetStoreType( LPCSTR sz ) { DWORD i; for (i = 0; i < stError; i++) { if (!_strnicmp(sz, gtypeinfo[i].tag, gtypeinfo[i].taglen)) { return i; } } return stError; } BOOL ParsePath( IN LPCSTR ipath, OUT LPSTR site, OUT LPSTR path, OUT LPSTR file, IN BOOL striptype ) { char sz[_MAX_PATH + 1]; char *c; char *p; DWORD type; assert(ipath && site && path); *site = 0; *path = 0; if (file) *file = 0; if (!CopyString(sz, ipath, _MAX_PATH)) return false; ConvertBackslashes(sz); // get start of site string type = GetStoreType(sz); p = sz + gtypeinfo[type].taglen; // there has to be at least a site c = strchr(p, '/'); if (!c) { strcpy(site, p); // SECURITy: ParsePath is a safe function. return true; } // copy site name *c = 0; strcpy(site, (striptype) ? p : sz); // SECURITy: ParsePath is a safe function. p = c + 1; // if no file parameter, include the file in the path parameter if (!file) { strcpy(path, p); // SECURITy: ParsePath is a safe function. return true; } // look for path in the middle for (c = p + strlen(p); p < c; c--) { if (*c == '/') { *c = 0; strcpy(path, p); // SECURITy: ParsePath is a safe function. p = c + 1; break; } } strcpy(file, p); // SECURITy: ParsePath is a safe function. return true; } static char ChangeLastChar( LPSTR sz, char newchar ) { char c; DWORD len; len = strlen(sz) - 1; c = sz[len]; sz[len] = newchar; return c; } static BOOL ReplaceFileName( LPSTR path, LPCSTR file, DWORD size ) { char *p; assert(path && *path && file && *file); for (p = path + strlen(path) - 1; *p; p--) { if (*p == '\\' || *p == '/') { CopyString(++p, file, size - (ULONG)(ULONG_PTR)(p - path)); return true; } } return false; } DWORD CALLBACK cbCopyProgress( LARGE_INTEGER TotalFileSize, // file size LARGE_INTEGER TotalBytesTransferred, // bytes transferred LARGE_INTEGER StreamSize, // bytes in stream LARGE_INTEGER StreamBytesTransferred, // bytes transferred for stream DWORD dwStreamNumber, // current stream DWORD dwCallbackReason, // callback reason HANDLE hSourceFile, // handle to source file HANDLE hDestinationFile, // handle to destination file LPVOID lpData // from CopyFileEx ) { Store *store = (Store *)lpData; store->setsize(TotalFileSize.QuadPart); store->setbytes(TotalBytesTransferred.QuadPart); store->progress(); if (querycancel()) { store->setbytes((LONGLONG)-1); store->progress(); return PROGRESS_CANCEL; } return PROGRESS_CONTINUE; } BOOL ReadFilePtr( LPSTR path, DWORD size ) { BOOL rc; HANDLE hptr; DWORD fsize; DWORD cb; LPSTR p; char ptrfile[MAX_PATH + 1]; char file[MAX_PATH + 1]; assert(path && *path); rc = false; // check for existance of file pointer if (!CopyString(ptrfile, path, _MAX_PATH)) return false; if (!ReplaceFileName(ptrfile, "file.ptr", DIMA(ptrfile))) return false; if (FileStatus(ptrfile)) return false; hptr = CreateFile(ptrfile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hptr == INVALID_HANDLE_VALUE) return false; // test validity of file pointer fsize = GetFileSize(hptr, NULL); if (!fsize || fsize > MAX_PATH) goto cleanup; // read it ZeroMemory(file, _MAX_PATH * sizeof(path[0])); if (!ReadFile(hptr, file, fsize, &cb, 0)) goto cleanup; if (cb != fsize) goto cleanup; rc = true; // trim string down to the CR for (p = file; *p; p++) { if (*p == 10 || *p == 13) { *p = 0; break; } } CopyString(path, file, size); dprint("%s\n", ptrfile); cleanup: // done if (hptr) CloseHandle(hptr); return rc; } #ifdef USE_INERROR DWORD inerror(DWORD error) { char *detail = NULL; DWORD iErr; DWORD len = 0; static char message[256]=""; if (error == ERROR_SUCCESS) return error; FormatMessage(FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle("wininet.dll"), error, 0, message, 256, NULL); EnsureTrailingCR(message); dprint("Internet error code: %d\n Message: %s\n", error, message); if (error != ERROR_INTERNET_EXTENDED_ERROR) return error; InternetGetLastResponseInfo(&iErr, NULL, &len); if (!len) return error; detail = (char *)LocalAlloc(LPTR, len + 1000); if (!detail) return error; if (!InternetGetLastResponseInfo(&iErr, (LPTSTR)detail, &len)) return error; dprint(detail); LocalFree(detail); return error; } #endif Store *gstores[MAX_STORES]; DWORD gcstores = 0; Store * FindStore( PCSTR name ) { DWORD i; for (i = 0; i < gcstores; i++) { if (gstores[i] && !strcmp((gstores[i])->name(), name)) return gstores[i]; } return NULL; } Store * AddStore( PCSTR name ) { DWORD type; Store *store; type = GetStoreType(name); switch (type) { case stUNC: store = new StoreUNC(); break; case stHTTP: case stHTTPS: store = new StoreHTTP(); break; case stError: default: SetLastError(ERROR_INVALID_PARAMETER); return NULL; } if (!store) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } store->assign(name); if (gcstores >= MAX_STORES) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return false; } if (!gcstores) ZeroMemory(gstores, sizeof(gstores)); gstores[gcstores] = store; gcstores++; return store; } Store * GetStore( PCSTR name ) { Store *store; store = FindStore(name); if (!store) store = AddStore(name); return store; } BOOL DeleteStore( Store *store ) { DWORD i; for (i = 0; i < gcstores; i++) { if (gstores[i] == store) { gstores[i] = NULL; delete store; return true; } } return false; } DWORD Store::assign(PCSTR name) { CopyStrArray(m_name, name); m_type = GetStoreType(name); m_flags = 0; return m_type; } char *Store::target() { return m_tpath; } BOOL Store::init() { if (m_flags & SF_DISABLED) return false; *m_tpath = 0; return true; } BOOL Store::ping() { return false; } BOOL Store::open(PCSTR rpath, PCSTR file) { CopyStrArray(m_rpath, rpath ? rpath : ""); CopyStrArray(m_file, file ? file : ""); return true; } VOID Store::close() { return; } BOOL Store::get(PCSTR trg) { pathcpy(m_tpath, trg, m_rpath, DIMA(m_tpath)); pathcat(m_tpath, m_file, DIMA(m_tpath)); EnsurePathExists(m_tpath, m_epath, DIMA(m_epath)); return true; } BOOL Store::copy(PCSTR rpath, PCSTR file, PCSTR trg) { if (m_flags & SF_DISABLED) return false; return true; } BOOL Store::progress() { SYSTEMTIME st; if (!*m_file) return true; // Do not condense these print statements into single lines. // They are split up to work with dbghelp's backspace filtering. switch (m_bytes) { case (LONGLONG)-1: eprint("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); eprint("cancelled \n"); SetLastError(ERROR_REQUEST_ABORTED); break; case 0: dprint("%s from %s: %ld bytes - ", m_file, m_name, m_size); eprint("\b%12ld ", 0); break; default: GetSystemTime(&st); if (st.wSecond != m_tic) eprint("\b\b\b\b\b\b\b\b\b\b\b\b%12ld", m_bytes); m_tic = st.wSecond; break; } if (m_bytes && (m_bytes == m_size)) { eprint("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); eprint("copied \n"); } return true; } void Store::setsize(LONGLONG size) { m_size = size; } void Store::setbytes(LONGLONG bytes) { m_bytes = bytes; } BOOL StoreUNC::get(PCSTR trg) { BOOL rc; DWORD err; char c; pathcpy(m_spath, m_name, m_rpath, DIMA(m_spath)); pathcat(m_spath, m_file, DIMA(m_spath)); Store::get(trg); do { // try to copy the file, first if (gdprint) rc = CopyFileEx(m_spath, m_tpath, cbCopyProgress, this, &gcancel, COPY_FILE_FAIL_IF_EXISTS); else rc = CopyFile(m_spath, m_tpath, true); if (rc) return true; err = GetLastError(); err = fixerror(err); if (err != ERROR_FILE_NOT_FOUND) { disperror(err, m_spath); return false; } // now try to uncompress the file c = ChangeLastChar(m_spath, '_'); if (!FileStatus(m_spath)) { rc = UncompressFile(m_spath, m_tpath); if (!rc) disperror(GetLastError(), m_spath); return rc; } ChangeLastChar(m_spath, c); // try to read from a file pointer } while (ReadFilePtr(m_spath, DIMA(m_spath))); err = GetLastError(); err = fixerror(err); disperror(err, m_spath); return false; } BOOL StoreUNC::copy(PCSTR rpath, PCSTR file, PCSTR trg) { if (!Store::copy(rpath, file, trg)) return false; if (!open(rpath, file)) return false; return get(trg); } BOOL StoreUNC::ping() { BOOL rc; CopyStrArray(m_spath, m_name); EnsureTrailingBackslash(m_spath); CatStrArray(m_spath, "pingme.txt"); rc = (GetFileAttributes(m_spath) == 0xFFFFFFFF); if (!rc) m_flags |= SF_DISABLED; return rc; } BOOL StoreInet::init() { char sz[_MAX_PATH]; static char uasz[_MAX_PATH] = ""; // internet handle is null, then we know from previous // attempts that it can't be opened, so bail if (!ghint) return false; if (!*uasz) { CopyStrArray(uasz, "Microsoft-Symbol-Server/"); CatStrArray(uasz, VER_PRODUCTVERSION_STR); } *m_spath = 0; *m_rpath = 0; *m_file = 0; ParsePath(m_name, m_site, sz, NULL, true); CopyStrArray(m_srpath, "/"); CatStrArray(m_srpath, sz); if (ghint == INVALID_HANDLE_VALUE) { ghint = InternetOpen(uasz, (gproxy) ? INTERNET_OPEN_TYPE_PROXY : INTERNET_OPEN_TYPE_PRECONFIG, gproxy, NULL, 0); if (!ghint) return false; dumpproxyinfo(); } if (m_hsite) return true; m_hsite = InternetConnect(ghint, m_site, m_port, NULL, NULL, m_service, 0, NULL);//m_context ? (DWORD_PTR)&m_context : NULL); if (!m_hsite) return false; return true; } BOOL StoreInet::open(PCSTR rpath, PCSTR file) { Store::open(rpath, file); CopyStrArray(m_spath, m_srpath); pathcat(m_spath, m_rpath, DIMA(m_spath)); return true; } BOOL StoreInet::copy(PCSTR rpath, PCSTR file, PCSTR trg) { BOOL rc; DWORD err; char cfile[MAX_PATH + 1]; char c; if (!Store::copy(rpath, file, trg)) return false; if (m_flags & SF_INTERNET_DISABLED) return false; // open and copy the file if (open(rpath, file)) { rc = get(trg); close(); return rc; } // if file wasn't found, look for a compressed version err = GetLastError(); if (err != ERROR_FILE_NOT_FOUND) return false; CopyStrArray(cfile, file); c = ChangeLastChar(cfile, '_'); if (!open(rpath, cfile)) { dprint("%s%s%s not found\n", gtypeinfo[m_type].tag, m_site, m_spath); return false; } rc = get(trg); close(); if (!rc) return false; // if we found a compressed version, expand it CopyStrArray(cfile, m_tpath); ChangeLastChar(m_tpath, c); rc = UncompressFile(cfile, m_tpath); DeleteFile(cfile); if (!rc) DeleteFile(m_tpath); return rc; } BOOL StoreInet::ping() { return open("", "pingme.txt"); } BOOL StoreHTTP::init() { BOOL rc; m_iflags = INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_KEEP_CONNECTION; m_service = INTERNET_SERVICE_HTTP; m_context = 0; if (m_type == stHTTPS) { m_port = INTERNET_DEFAULT_HTTPS_PORT; m_iflags |= INTERNET_FLAG_SECURE; } else { m_port = INTERNET_DEFAULT_HTTP_PORT; } *m_srpath = 0; return StoreInet::init(); } BOOL StoreHTTP::open(PCSTR rpath, PCSTR file) { DWORD err = ERROR_NOT_FOUND; Store::open(rpath, file); CopyStrArray(m_spath, m_srpath); pathcat(m_spath, m_rpath, DIMA(m_spath)); pathcat(m_spath, m_file, DIMA(m_spath)); ConvertBackslashes(m_spath); close(); m_hfile = HttpOpenRequest(m_hsite, "GET", m_spath, HTTP_VERSION, NULL, NULL, m_iflags, 0); if (!m_hfile) goto error; err = fileinfo(); if (!err) return true; error: close(); SetLastError(err); return false; } VOID StoreHTTP::close() { DWORD err; if (!m_hfile) return; // InternetCloseHandle resets last error to zero. // Preserve it and restore it afterwards. err = GetLastError(); InternetCloseHandle(m_hfile); if (err) SetLastError(err); m_hfile = 0; } DWORD StoreHTTP::fileinfo() { BOOL rc; DWORD err; DWORD status; DWORD cbstatus; DWORD index; DWORD cbsize; #ifdef PROXYTEST dprint("FILE %s\n", m_spath); #endif do { err = request(); if (err != ERROR_SUCCESS) return err; index = 0; m_size = 0; cbsize = sizeof(m_size); rc = HttpQueryInfo(m_hfile, HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER, &m_size, &cbsize, &index); if (!rc) { if (GetLastError()) return err; return ERROR_INTERNET_EXTENDED_ERROR; } index = 0; cbstatus = sizeof(status); rc = HttpQueryInfo(m_hfile, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &cbstatus, &index); if (!rc) { if (GetLastError()) return err; return ERROR_INTERNET_EXTENDED_ERROR; } switch (status) { case HTTP_STATUS_DENIED: // need a valid login? // dprint("status HTTP_STATUS_DENIED\n"); err = prompt(m_hfile, ERROR_INTERNET_INCORRECT_PASSWORD); // user entered a password - try again if (err == ERROR_INTERNET_FORCE_RETRY) break; // user cancelled m_flags |= SF_DISABLED; return ERROR_NOT_READY; case HTTP_STATUS_PROXY_AUTH_REQ: // dprint("status HTTP_STATUS_PROXY_AUTH_REQ\n"); err = prompt(m_hfile, err); // user entered a password - try again if (err == ERROR_INTERNET_FORCE_RETRY) break; // user cancelled m_flags |= SF_INTERNET_DISABLED; return ERROR_NOT_READY; case HTTP_STATUS_FORBIDDEN: // dprint("status HTTP_STATUS_FORBIDDEN\n"); m_flags |= SF_DISABLED; return ERROR_ACCESS_DENIED; case HTTP_STATUS_NOT_FOUND: // dprint("status HTTP_STATUS_NOT_FOUND\n"); return ERROR_FILE_NOT_FOUND; case HTTP_STATUS_OK: // dprint("status HTTP_STATUS_OK\n"); return ERROR_SUCCESS; } } while (err == ERROR_INTERNET_FORCE_RETRY); return ERROR_INTERNET_EXTENDED_ERROR; } DWORD StoreHTTP::request() { DWORD err = ERROR_SUCCESS; while (!HttpSendRequest(m_hfile, NULL, 0, NULL, 0)) { err = GetLastError(); switch (err) { // These cases get input from the user in oder to try again. case ERROR_INTERNET_INCORRECT_PASSWORD: case ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED: err = prompt(m_hfile, err); if (err != ERROR_SUCCESS && err != ERROR_INTERNET_FORCE_RETRY) { err = ERROR_ACCESS_DENIED; return err; } break; // These cases get input from the user in order to try again. // However, if the user bails, don't use this internet // connection again in this session. case ERROR_INTERNET_INVALID_CA: case ERROR_INTERNET_SEC_CERT_DATE_INVALID: case ERROR_INTERNET_SEC_CERT_CN_INVALID: case ERROR_INTERNET_POST_IS_NON_SECURE: err = prompt(m_hfile, err); if (err != ERROR_SUCCESS && err != ERROR_INTERNET_FORCE_RETRY) { m_flags |= SF_DISABLED; err = ERROR_NOT_READY; return err; } break; // no go - give up the channel case ERROR_INTERNET_SECURITY_CHANNEL_ERROR: m_flags |= SF_DISABLED; err = ERROR_NOT_READY; return err; // Tell the user something went wrong and get out of here. case ERROR_INTERNET_HTTP_TO_HTTPS_ON_REDIR: default: prompt(m_hfile, err); return err; } } return err; } DWORD StoreHTTP::prompt(HINTERNET hreq, DWORD err) { if (gopts & SSRVOPT_UNATTENDED) return err; if (!ghwnd) ghwnd = GetDesktopWindow(); if (!ghwnd) return err; err = InternetErrorDlg(ghwnd, hreq, err, FLAGS_ERROR_UI_FILTER_FOR_ERRORS | FLAGS_ERROR_UI_FLAGS_GENERATE_DATA | FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS, NULL); return err; } BOOL StoreHTTP::get(PCSTR trg) { DWORD read; DWORD written; DWORD err = 0; BYTE *buf; BOOL rc = false; HANDLE hf = INVALID_HANDLE_VALUE; ULONG64 copied; buf = (BYTE *)LocalAlloc(LPTR, CHUNK_SIZE); if (!buf) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); return false; } Store::get(trg); hf = CreateFile(m_tpath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hf == INVALID_HANDLE_VALUE) goto cleanup; m_bytes = 0; do { if (!progress()) goto cleanup; rc = InternetReadFile(m_hfile, (LPVOID)buf, CHUNK_SIZE, &read); if (!rc || !read) break; rc = WriteFile(hf, (LPVOID)buf, read, &written, NULL); m_bytes += written; } while (rc); cleanup: // if there was an error, save it and set it later if (!err) err = GetLastError(); disperror(err || GetLastError(), m_spath); // If target file is open, close it. if (hf != INVALID_HANDLE_VALUE) CloseHandle(hf); // free the memory LocalFree(buf); SetLastError(err); return err ? false : true; } BOOL StoreHTTP::progress() { Store::progress(); if (!m_bytes || !querycancel()) return true; setbytes((LONGLONG)-1); Store::progress(); return false; }