[
  {
    "path": ".gitignore",
    "content": "/build/\n\n# Prerequisites\n*.d\n\n# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Linker output\n*.ilk\n*.map\n*.exp\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n# Debug files\n*.dSYM/\n*.su\n*.idb\n*.pdb\n\n# Kernel Module Compile Results\n*.mod*\n*.cmd\n.tmp_versions/\nmodules.order\nModule.symvers\nMkfile.old\ndkms.conf\n"
  },
  {
    "path": ".mailmap",
    "content": "Minqi Pan <pmq2001@gmail.com>"
  },
  {
    "path": ".travis.yml",
    "content": "language: c\n\nos:\n  - linux\n  - osx\n\ncompiler:\n  - gcc\n  - clang\n\nscript:\n  - cmake --version\n  - mkdir build && cd build\n  - cmake -DBUILD_TESTS=ON .. || exit $?\n  - cmake --build . || exit $?\n"
  },
  {
    "path": "AUTHORS",
    "content": "# Authors ordered by first contribution.\n\nMinqi Pan <pmq2001@gmail.com>\nVenkat Ram <venkatpetit@gmail.com>\n\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "# Copyright (c) 2020 Minqi Pan et al.\n# \n# This file is part of libautoupdate, distributed under the MIT License\n# For full terms see the included LICENSE file\n\nPROJECT(libautoupdate C)\nCMAKE_MINIMUM_REQUIRED(VERSION 2.6)\n\nFIND_PACKAGE(ZLIB)\n\nINCLUDE_DIRECTORIES(src include ${ZLIB_INCLUDE_DIR})\nFILE(GLOB SRC_H include/autoupdate.h)\nFILE(GLOB SRC_AUTOUPDATE src/*.c src/*.h)\nADD_LIBRARY(autoupdate ${SRC_H} ${SRC_AUTOUPDATE})\n\nIF(BUILD_TESTS)\n  ENABLE_TESTING()\n  ADD_TEST(autoupdate_tests autoupdate_tests --help)\n  FILE(GLOB SRC_TEST tests/*.c)\n  ADD_EXECUTABLE(autoupdate_tests ${SRC_TEST})\n  TARGET_LINK_LIBRARIES(autoupdate_tests autoupdate ${ZLIB_LIBRARIES})\n  if(WIN32)\n    TARGET_LINK_LIBRARIES(autoupdate_tests shlwapi.lib Ws2_32.lib)\n  endif()\nENDIF()\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Minqi Pan et al.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Libautoupdate\n\nCross-platform C library that enables your application to auto-update itself in place.\n\n[![Build Status](https://travis-ci.org/pmq20/libautoupdate.svg?branch=master)](https://travis-ci.org/pmq20/libautoupdate)\n[![Build status](https://ci.appveyor.com/api/projects/status/sjdyfwd768lh187f/branch/master?svg=true)](https://ci.appveyor.com/project/pmq20/libautoupdate/branch/master)\n\n![Terminal simulation of a simple auto-update](https://github.com/pmq20/libautoupdate/raw/master/doc/libautoupdate.gif)\n\n## API\n\nThere is only one public API, i.e. `autoupdate()`.\n\n```C\nint autoupdate(argc, argv, host, port, path, current)\n```\n\nIt accepts the following arguments:\n\n- the 1st and 2nd arguments are the same as those passed to `main()`\n- `host` is the host name of the update server to communicate with\n- `port` is the port of the server, which is a string on Windows and a 16-bit integer on macOS / Linux\n- `path` is the paramater passed to the HTTP/1.0 HEAD request of the Round 1\n- `current` is the current version string to be compared with what is returned from the server\n  - a new version is considered detected if this string is not a substring of the server's reply\n\nIt never returns if a new version was detected and auto-update was successfully performed.\nOtherwise, it returns one of the following integers to indicate the situation:\n\n|  Return Value  | Indication                                                                                  |\n|:--------------:|---------------------------------------------------------------------------------------------|\n|        0       | Latest version confirmed. No need to update                                                 |\n|        1       | Auto-update shall not proceed due to environment variable `CI` being set                    |\n|        2       | Auto-update process failed prematurely and detailed errors are printed to stderr            |\n|        3       | Failed to restart after replacing itself with the new version                               |\n|        4       | Auto-update shall not proceed due to being already checked in the last 24 hours             |\n\n## Communication\n\n### Round 1\n\nLibautoupdate first makes a HTTP/1.0 HEAD request to the server, in order to peek the latest version string.\n\n    Libautoupdate -- HTTP/1.0 HEAD request --> Server\n\nThe server is expected to repond with `HTTP 302 Found` and provide a `Location` header.\n\nIt then compares the content of `Location` header with the current version string.\nIt proceeds to Round 2 if the current version string is NOT a sub-string of the `Location` header.\n\n### Round 2\n\nLibautoupdate makes a full HTTP/1.0 GET request to the `Location` header of the last round.\n\n    Libautoupdate -- HTTP/1.0 GET request --> Server\n\nThe server is expected to respond with `200 OK` transferring the new release itself.\n\nBased on the `Content-Type` header received, an addtional inflation operation might be performed:\n- `Content-Type: application/x-gzip`: Gzip Inflation is performed\n- `Content-Type: application/zip`: Deflate compression is assumed and the first file is inflated and used\n\n## Self-replacing\n\nAfter 2 rounds of communication with the server,\nlibautoupdate will then proceeds with a self-replacing process,\ni.e. the program replaces itself in-place with the help of the system temporary directory,\nafter which it restarts itself with the new release.\n\n## Examples\n\nJust call `autoupdate()` at the beginning of your `main()`,\nbefore all actual logic of your application.\nSee the following code for examples.\n\n### Windows\n\n```C\n#include <autoupdate.h>\n\nint wmain(int argc, wchar_t *wargv[])\n{\n\tint autoupdate_result;\n\n\tautoupdate_result = autoupdate(\n\t\targc,\n\t\twargv,\n\t\t\"enclose.io\",\n\t\t\"80\",\n\t\t\"/nodec/nodec-x64.exe\"\n\t\t\"v1.1.0\"\n\t);\n\n\t/* \n\t\tactual logic of your application\n\t\t...\n\t*/\n}\n```\n\n### macOS / Linux\n\n```C\n#include <autoupdate.h>\n\nint main(int argc, char *argv[])\n{\n\tint autoupdate_result;\n\n\tautoupdate_result = autoupdate(\n\t\targc,\n\t\targv,\n\t\t\"enclose.io\",\n\t\t80,\n\t\t\"/nodec/nodec-darwin-x64\"\n\t\t\"v1.1.0\"\n\t);\n\n\t/* \n\t\tactual logic of your application\n\t\t...\n\t*/\n}\n```\n\n## Hints\n\n- Set environment variable `CI=true` will prevent auto-updating\n- Remove the file `~/.libautoupdate` will remove the once-per-24-hours check limit\n\n## To-do\n\n- Cater to bad network connection situations\n- Print more information about the new version\n- Use better error messages when the user did not have permissions to move the new version into the destination directory\n- Move the old binary to the system temporary directory, yet not deleting it.\n  - The Operating System will delete it when restarted/tmpdir-full\n  - Add facility to restore/rollback to the old file once the new version went wrong\n\n## Authors\n\n[Minqi Pan et al.](https://raw.githubusercontent.com/pmq20/libautoupdate/master/AUTHORS)\n\n## License\n\n[MIT](https://raw.githubusercontent.com/pmq20/libautoupdate/master/LICENSE)\n\n## See Also\n\n- [Node.js Packer](https://github.com/pmq20/node-packer): Packing your Node.js application into a single executable.\n- [Ruby Packer](https://github.com/pmq20/ruby-packer): Packing your Ruby application into a single executable.\n"
  },
  {
    "path": "appveyor.yml",
    "content": "version: '{build}'\r\n\r\nenvironment:\r\n  matrix:\r\n  - GENERATOR: \"Visual Studio 14 2015 Win64\"\r\n    ARCH: 64\r\n    PlatformToolset: v140\r\n    Platform: x64\r\n  - GENERATOR: \"Visual Studio 12 2013 Win64\"\r\n    ARCH: 64\r\n    PlatformToolset: v120\r\n    Platform: x64\r\n\r\nbuild_script:\r\n- ps: |\r\n    nuget install zlib.$env:PlatformToolset.windesktop.msvcstl.dyn.rt-dyn\r\n\r\n    cmake --version\r\n\r\n    mkdir build\r\n\r\n    cd build\r\n\r\n    cmake -DBUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Release -G\"$env:GENERATOR\" -DZLIB_INCLUDE_DIR:PATH=C:\\projects\\libautoupdate\\zlib.$env:PlatformToolset.windesktop.msvcstl.dyn.rt-dyn.1.2.8.8\\build\\native\\include -DZLIB_LIBRARY_RELEASE:FILEPATH=C:\\projects\\libautoupdate\\zlib.$env:PlatformToolset.windesktop.msvcstl.dyn.rt-dyn.1.2.8.8\\lib\\native\\$env:PlatformToolset\\windesktop\\msvcstl\\dyn\\rt-dyn\\$env:Platform\\RelWithDebInfo\\zlib.lib ..\r\n\r\n    cmake --build . --config Release\r\n"
  },
  {
    "path": "include/autoupdate.h",
    "content": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n * For full terms see the included LICENSE file\n */\n\n#ifndef AUTOUPDATE_H_8C141CA2\n#define AUTOUPDATE_H_8C141CA2\n\n#ifdef _WIN32\n\n#include <wchar.h>\nint autoupdate(\n\tint argc,\n\twchar_t *wargv[],\n\tconst char *host,\n\tconst char *port,\n\tconst char *path,\n\tconst char *current,\n\tshort force\n);\n\n#else\n\n#include <stdint.h>\nint autoupdate(\n\tint argc,\n\tchar *argv[],\n\tconst char *host,\n\tuint16_t port,\n\tconst char *path,\n\tconst char *current,\n\tshort force\n);\n\n#endif // _WIN32\n\n#endif /* end of include guard: AUTOUPDATE_H_8C141CA2 */\n"
  },
  {
    "path": "libautoupdate.gyp",
    "content": "# Copyright (c) 2020 Minqi Pan et al.\n# \n# This file is part of libautoupdate, distributed under the MIT License\n# For full terms see the included LICENSE file\n\n{\n  'targets': [\n    {\n      'target_name': 'libautoupdate',\n      'type': 'static_library',\n      'sources': [\n        'include/autoupdate.h',\n        'src/autoupdate.c',\n        'src/autoupdate_internal.h',\n        'src/exepath.c',\n        'src/inflate.c',\n        'src/tmpf.c',\n        'src/utils.c',\n      ],\n      'include_dirs': [\n        'include',\n        '../zlib',\n      ],\n    },\n  ],\n}\n"
  },
  {
    "path": "src/autoupdate.c",
    "content": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n * For full terms see the included LICENSE file\n */\n\n#include \"autoupdate.h\"\n#include \"autoupdate_internal.h\"\n#include \"zlib.h\"\n\n#ifdef _WIN32\n\n#include <assert.h>\n#include <string.h>\n#include <winsock2.h>\n#include <ws2tcpip.h>\n#include <stdio.h>\n#include <conio.h>\n#include <stdint.h>\n#include <stdlib.h> /* exit */\n#include <wchar.h>\n\nint autoupdate(\n\tint argc,\n\twchar_t *wargv[],\n\tconst char *host,\n\tconst char *port,\n\tconst char *path,\n\tconst char *current,\n\tshort force\n)\n{\n\tWSADATA wsaData;\n\n\tif (!force && !autoupdate_should_proceed()) {\n\t\treturn 1;\n\t}\n\n\tif (!force && !autoupdate_should_proceed_24_hours(argc, wargv, 0)) {\n\t\treturn 4;\n\t}\n\n\t// Initialize Winsock\n\tint iResult = WSAStartup(MAKEWORD(2,2), &wsaData);\n\tif (iResult != 0) {\n\t\tfprintf(stderr, \"Auto-update Failed: WSAStartup failed with %d\\n\", iResult);\n\t\treturn 2;\n\t}\n\n\tstruct addrinfo *result = NULL,\n\t\t*ptr = NULL,\n\t\thints;\n\n\tZeroMemory(&hints, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = SOCK_STREAM;\n\thints.ai_protocol = IPPROTO_TCP;\n\n\t// Resolve the server address and port\n\tiResult = getaddrinfo(host, port, &hints, &result);\n\tif (iResult != 0) {\n\t\tfprintf(stderr, \"Auto-update Failed: getaddrinfo failed with %d\\n\", iResult);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\n\tSOCKET ConnectSocket = INVALID_SOCKET;\n\n\t// Attempt to connect to the first address returned by\n\t// the call to getaddrinfo\n\tptr = result;\n\n\t// Create a SOCKET for connecting to server\n\tConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,\n\t\tptr->ai_protocol);\n\n\tif (ConnectSocket == INVALID_SOCKET) {\n\t\tfprintf(stderr, \"Auto-update Failed: Error at socket() with %d\\n\", WSAGetLastError());\n\t\tfreeaddrinfo(result);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\n\t// Connect to server.\n\tiResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);\n\tif (iResult == SOCKET_ERROR) {\n\t\tclosesocket(ConnectSocket);\n\t\tConnectSocket = INVALID_SOCKET;\n\t}\n\tfreeaddrinfo(result);\n\tif (ConnectSocket == INVALID_SOCKET) {\n\t\tfprintf(stderr, \"Auto-update Failed: connect failed on %s and port %s\\n\", host, port);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\tif (5 != send(ConnectSocket, \"HEAD \", 5, 0) ||\n\t\tstrlen(path) != send(ConnectSocket, path, strlen(path), 0) ||\n\t\t11 != send(ConnectSocket, \" HTTP/1.0\\r\\n\", 11, 0) ||\n\t\t6 != send(ConnectSocket, \"Host: \", 6, 0) ||\n\t\tstrlen(host) != send(ConnectSocket, host, strlen(host), 0) ||\n\t\t4 != send(ConnectSocket, \"\\r\\n\\r\\n\", 4, 0)) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: send failed with %d\\n\", WSAGetLastError());\n\t\t\tclosesocket(ConnectSocket);\n\t\t\tWSACleanup();\n\t\t\treturn 2;\n\t}\n\n\tchar response[1024 * 10 + 1]; // 10KB\n\tint bytes, total;\n\ttotal = sizeof(response) - 2;\n\tlong long received = 0;\n\tdo {\n\t\tbytes = recv(ConnectSocket, response + received, total - received, 0);\n\t\tif (bytes < 0) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: recv failed with %d\\n\", WSAGetLastError());\n\t\t\tclosesocket(ConnectSocket);\n\t\t\tWSACleanup();\n\t\t\treturn 2;\n\t\t}\n\t\tif (bytes == 0) {\n\t\t\t/* EOF */\n\t\t\t*(response + received) = 0;\n\t\t\tbreak;\n\t\t}\n\t\treceived += bytes;\n\t} while (received < total);\n\tif (received == total) {\n\t\tfprintf(stderr, \"Auto-update Failed: read causes buffer full\\n\");\n\t\tclosesocket(ConnectSocket);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\n\t// shutdown the connection for sending since no more data will be sent\n\t// the client can still use the ConnectSocket for receiving data\n\tiResult = shutdown(ConnectSocket, SD_SEND);\n\tif (iResult == SOCKET_ERROR) {\n\t\tfprintf(stderr, \"Auto-update Failed: shutdown failed with %d\\n\", WSAGetLastError());\n\t\tclosesocket(ConnectSocket);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\n\tassert(received < total);\n\tsize_t len = strlen(response);\n\tshort again_302 = 0;\nparse_location_header:\n\tassert(len <= total);\n\tchar *new_line = NULL;\n\tchar *found = NULL;\n\tsize_t i = 0;\n\tresponse[sizeof(response) - 1] = 0;\n\twhile (i < len) {\n\t\tnew_line = strstr(response + i, \"\\r\\n\");\n\t\tif (NULL == new_line) {\n\t\t\tbreak;\n\t\t}\n\t\t*new_line = 0;\n\t\tif (0 == strncmp(response + i, \"Location: \", 10)) {\n\t\t\tfound = response + i + 10;\n\t\t\tbreak;\n\t\t}\n\t\t*new_line = '\\r';\n\t\ti = new_line - response + 2;\n\t}\n\tif (!found) {\n\t\tfprintf(stderr, \"Auto-update Failed: failed to find a Location header\\n\");\n\t\treturn 2;\n\t}\n\tif (!again_302) {\n\t\tif (strstr(found, current)) {\n\t\t\t/* Latest version confirmed. No need to update */\n\t\t\tautoupdate_should_proceed_24_hours(argc, wargv, 1);\n\t\t\treturn 0;\n\t\t} else {\n\t\t\tfprintf(stderr, \"Hint: to disable auto-update, run with environment variable CI=true\\n\");\n\t\t\tfflush(stderr);\n\t\t}\n\t}\n\n\tchar *url = found;\n\tfprintf(stderr, \"Downloading update from %s\\n\", url);\n\tfflush(stderr);\n\n\tchar *host2;\n\tchar *port2 = \"80\";\n\tif (strlen(url) >= 8 && 0 == strncmp(\"https://\", url, 8)) {\n\t\thost2 = url + 8;\n\t} else if (strlen(url) >= 7 && 0 == strncmp(\"http://\", url, 7)) {\n\t\thost2 = url + 7;\n\t} else {\n\t\tfprintf(stderr, \"Auto-update Failed: failed to find http:// or https:// at the beginning of URL %s\\n\", url);\n\t\treturn 2;\n\t}\n\tchar *found_slash = strchr(host2, '/');\n\tchar *request_path;\n\tif (NULL == found_slash) {\n\t\trequest_path = \"/\";\n\t} else {\n\t\trequest_path = found_slash;\n\t\t*found_slash = 0;\n\t}\n\n\tresult = NULL;\n\tptr = NULL;\n\tZeroMemory(&hints, sizeof(hints));\n\thints.ai_family = AF_UNSPEC;\n\thints.ai_socktype = SOCK_STREAM;\n\thints.ai_protocol = IPPROTO_TCP;\n\n\t// Resolve the server address and port\n\tiResult = getaddrinfo(host2, port2, &hints, &result);\n\tif (iResult != 0) {\n\t\tfprintf(stderr, \"Auto-update Failed: getaddrinfo failed with %d\\n\", iResult);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\n\tConnectSocket = INVALID_SOCKET;\n\n\t// Attempt to connect to the first address returned by\n\t// the call to getaddrinfo\n\tptr = result;\n\n\t// Create a SOCKET for connecting to server\n\tConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);\n\n\tif (ConnectSocket == INVALID_SOCKET) {\n\t\tfprintf(stderr, \"Auto-update Failed: Error at socket() with %d\\n\", WSAGetLastError());\n\t\tfreeaddrinfo(result);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\t// Connect to server.\n\tiResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);\n\tif (iResult == SOCKET_ERROR) {\n\t\tclosesocket(ConnectSocket);\n\t\tConnectSocket = INVALID_SOCKET;\n\t}\n\tfreeaddrinfo(result);\n\tif (ConnectSocket == INVALID_SOCKET) {\n\t\tfprintf(stderr, \"Auto-update Failed: connect failed on %s and port %s\\n\", host2, port2);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\tif (NULL != found_slash) {\n\t\t*found_slash = '/';\n\t}\n\tif (4 != send(ConnectSocket, \"GET \", 4, 0) ||\n\t\tstrlen(request_path) != send(ConnectSocket, request_path, strlen(request_path), 0) ||\n\t\t11 != send(ConnectSocket, \" HTTP/1.0\\r\\n\", 11, 0)) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: send failed with %d\\n\", WSAGetLastError());\n\t\t\tclosesocket(ConnectSocket);\n\t\t\tWSACleanup();\n\t\t\treturn 2;\n\t}\n\tif (NULL != found_slash) {\n\t\t*found_slash = 0;\n\t}\n\tif (6 != send(ConnectSocket, \"Host: \", 6, 0) ||\n\t\tstrlen(host2) != send(ConnectSocket, host2, strlen(host2), 0) ||\n\t\t4 != send(ConnectSocket, \"\\r\\n\\r\\n\", 4, 0)) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: send failed with %d\\n\", WSAGetLastError());\n\t\t\tclosesocket(ConnectSocket);\n\t\t\tWSACleanup();\n\t\t\treturn 2;\n\t}\n\n\t// Read the header\n\ttotal = sizeof(response) - 2;\n\tresponse[sizeof(response) - 1] = 0;\n\treceived = 0;\n\tchar *header_end = NULL;\n\tdo {\n\t\tbytes = recv(ConnectSocket, response + received, total - received, 0);\n\t\tif (bytes < 0) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: recv failed with %d\\n\", WSAGetLastError());\n\t\t\tclosesocket(ConnectSocket);\n\t\t\tWSACleanup();\n\t\t\treturn 2;\n\t\t}\n\t\tif (bytes == 0) {\n\t\t\t/* EOF */\n\t\t\t*(response + received) = 0;\n\t\t\tbreak;\n\t\t}\n\t\t*(response + received + bytes) = 0;\n\t\theader_end = strstr(response + received, \"\\r\\n\\r\\n\");\n\t\treceived += bytes;\n\t\tif (header_end) {\n\t\t\tbreak;\n\t\t}\n\t} while (received < total);\n\tif (NULL == header_end) {\n\t\tfprintf(stderr, \"Auto-update Failed: failed to find the end of the response header\\n\");\n\t\tclosesocket(ConnectSocket);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\tassert(received <= total);\n\n\t// Possible new 302\n\tif (received > 13 && (\n\t\t0 == strncmp(response, \"HTTP/1.1 302 \", 13) ||\n\t\t0 == strncmp(response, \"HTTP/1.0 302 \", 13))) {\n\t\t\tlen = received;\n\t\t\tagain_302 = 1;\n\t\t\tgoto parse_location_header;\n\t}\n\n\t// Parse the header\n\tlen = received;\n\tassert(len <= total);\n\tnew_line = NULL;\n\tlong long found_length = -1;\n\ti = 0;\n\tresponse[sizeof(response) - 1] = 0;\n\twhile (i < len) {\n\t\tnew_line = strstr(response + i, \"\\r\\n\");\n\t\tif (NULL == new_line) {\n\t\t\tbreak;\n\t\t}\n\t\t*new_line = 0;\n\t\tif (0 == strncmp(response + i, \"Content-Length: \", 16)) {\n\t\t\tfound_length = atoll(response + i + 16);\n\t\t\tbreak;\n\t\t}\n\t\t*new_line = '\\r';\n\t\ti = new_line - response + 2;\n\t}\n\tif (-1 == found_length) {\n\t\tfprintf(stderr, \"Auto-update Failed: failed to find a Content-Length header\\n\");\n\t\tclosesocket(ConnectSocket);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\tif (0 == found_length) {\n\t\tfprintf(stderr, \"Auto-update Failed: found a Content-Length header of zero\\n\");\n\t\tclosesocket(ConnectSocket);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\tassert(found_length > 0);\n\t// Read the body\n\t// header_end -> \\r\\n\\r\\n\n\tassert(header_end);\n\tassert(header_end + 4 <= response + received);\n\t// put the rest of over-read content when reading header\n\tsize_t the_rest = response + received - (header_end + 4);\n\tchar *body_buffer = (char *)(malloc(found_length));\n\tif (NULL == body_buffer) {\n\t\tfprintf(stderr, \"Auto-update Failed: Insufficient memory\\n\");\n\t\tclosesocket(ConnectSocket);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\tmemcpy(body_buffer, (header_end + 4), the_rest);\n\tchar *body_buffer_ptr = body_buffer + the_rest;\n\tchar *body_buffer_end = body_buffer + found_length;\n\t// read the remaining body\n\treceived = the_rest;\n\tfprintf(stderr, \"\\r%lld / %lld bytes finished (%lld%%)\",  received, found_length, received*100LL/found_length);\n\tfflush(stderr);\n\twhile (received < found_length) {\n\t\tsize_t space = 100 * 1024;\n\t\tif (space > body_buffer_end - body_buffer_ptr) {\n\t\t\tspace = body_buffer_end - body_buffer_ptr;\n\t\t}\n\t\tbytes = recv(ConnectSocket, body_buffer_ptr, space, 0);\n\t\tif (bytes < 0) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: read failed\\n\");\n\t\t\tfree(body_buffer);\n\t\t\tclosesocket(ConnectSocket);\n\t\t\tWSACleanup();\n\t\t\treturn 2;\n\t\t}\n\t\tif (bytes == 0) {\n\t\t\t/* EOF */\n\t\t\tbreak;\n\t\t}\n\t\treceived += bytes;\n\t\tbody_buffer_ptr += bytes;\n\t\tfprintf(stderr, \"\\r%lld / %lld bytes finished (%lld%%)\",  received, found_length, received*100LL/found_length);\n\t\tfflush(stderr);\n\t}\n\tif (received != found_length) {\n\t\tassert(received < found_length);\n\t\tfprintf(stderr, \"Auto-update Failed: prematurely reached EOF after reading %lld bytes\\n\", received);\n\t\tclosesocket(ConnectSocket);\n\t\tWSACleanup();\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tfprintf(stderr, \"\\n\");\n\tfflush(stderr);\n\t// shutdown the connection for sending since no more data will be sent\n\t// the client can still use the ConnectSocket for receiving data\n\tiResult = shutdown(ConnectSocket, SD_SEND);\n\tif (iResult == SOCKET_ERROR) {\n\t\tfprintf(stderr, \"Auto-update Failed: shutdown failed with %d\\n\", WSAGetLastError());\n\t\tclosesocket(ConnectSocket);\n\t\tWSACleanup();\n\t\treturn 2;\n\t}\n\t// Inflate to a file\n\tfprintf(stderr, \"Inflating\");\n\tfflush(stderr);\n\tstruct ZIPLocalFileHeader *h = (struct ZIPLocalFileHeader *)body_buffer;\n\tif (!(0x04034b50 == h->signature && 8 == h->compressionMethod)) {\n\t\tfprintf(stderr, \"Auto-update Failed: We only support a zip file containing \"\n\t\t\t\"one Deflate compressed file for the moment.\\n\"\n\t\t\t\"Pull requests are welcome on GitHub at \"\n\t\t\t\"https://github.com/pmq20/libautoupdate\\n\");\n\t}\n\t// skip the Local File Header\n\tunsigned full_length = found_length - sizeof(struct ZIPLocalFileHeader) - h->fileNameLength;\n\tunsigned half_length = full_length / 2;\n\tunsigned uncompLength = full_length;\n\n\t/* windowBits is passed < 0 to tell that there is no zlib header.\n\t* Note that in this case inflate *requires* an extra \"dummy\" byte\n\t* after the compressed stream in order to complete decompression and\n\t* return Z_STREAM_END.\n\t*/\n\tchar* uncomp = (char*)calloc(sizeof(char), uncompLength + 1);\n\tif (NULL == uncomp) {\n\t\tfprintf(stderr, \"Auto-update Failed: Insufficient memory\\n\");\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\n\tz_stream strm;\n\tstrm.next_in = (Bytef *)(body_buffer + sizeof(struct ZIPLocalFileHeader) + h->fileNameLength);\n\tstrm.avail_in = found_length;\n\tstrm.total_out = 0;\n\tstrm.zalloc = Z_NULL;\n\tstrm.zfree = Z_NULL;\n\n\tshort done = 0;\n\n\tif (inflateInit2(&strm, -MAX_WBITS) != Z_OK) {\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\tfprintf(stderr, \"Auto-update Failed: inflateInit2 failed\\n\");\n\t\treturn 2;\n\t}\n\n\twhile (!done) {\n\t\t// If our output buffer is too small\n\t\tif (strm.total_out >= uncompLength) {\n\t\t\t// Increase size of output buffer\n\t\t\tchar* uncomp2 = (char*)calloc(sizeof(char), uncompLength + half_length + 1);\n\t\t\tif (NULL == uncomp2) {\n\t\t\t\tfree(uncomp);\n\t\t\t\tfree(body_buffer);\n\t\t\t\tfprintf(stderr, \"Auto-update Failed: calloc failed\\n\");\n\t\t\t\treturn 2;\n\t\t\t}\n\t\t\tmemcpy(uncomp2, uncomp, uncompLength);\n\t\t\tuncompLength += half_length;\n\t\t\tfree(uncomp);\n\t\t\tuncomp = uncomp2;\n\t\t}\n\n\t\tstrm.next_out = (Bytef *)(uncomp + strm.total_out);\n\t\tstrm.avail_out = uncompLength - strm.total_out;\n\n\t\t// Inflate another chunk.\n\t\tint err = inflate(&strm, Z_SYNC_FLUSH);\n\t\tif (err == Z_STREAM_END) {\n\t\t\tdone = 1;\n\t\t}\n\t\telse if (err != Z_OK) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: inflate failed with %d\\n\", err);\n\t\t\tfree(uncomp);\n\t\t\tfree(body_buffer);\n\t\t\treturn 2;\n\t\t}\n\t}\n\n\tif (inflateEnd(&strm) != Z_OK) {\n\t\tfprintf(stderr, \"Auto-update Failed: inflateInit2 failed\\n\");\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\n\twchar_t *tmpdir = autoupdate_tmpdir();\n\tif (NULL == tmpdir) {\n\t\tfprintf(stderr, \"Auto-update Failed: no temporary folder found\\n\");\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\t/* Windows paths can never be longer than this. */\n\tconst size_t exec_path_len = 32768;\n\twchar_t exec_path[32768];\n\tDWORD utf16_len = GetModuleFileNameW(NULL, exec_path, exec_path_len);\n\tif (0 == utf16_len) {\n\t\tfprintf(stderr, \"Auto-update Failed: GetModuleFileNameW failed with GetLastError=%d\\n\", GetLastError());\n\t\tfree((void*)(tmpdir));\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tif (tmpdir[0] != exec_path[0]) {\n\t\tfree((void*)(tmpdir));\n\t\ttmpdir = wcsdup(exec_path);\n\t\twchar_t *backslash = wcsrchr(tmpdir, L'\\\\');\n\t\tif (NULL == backslash) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: Cannot find an approriate tmpdir with %S\\n\", tmpdir);\n\t\t\tfree((void*)(tmpdir));\n\t\t\tfree(uncomp);\n\t\t\tfree(body_buffer);\n\t\t\treturn 2;\n\t\t}\n\t\t*backslash = 0;\n\t}\n\twchar_t *tmpf = autoupdate_tmpf(tmpdir, \"exe\");\n\tif (NULL == tmpf) {\n\t\tfprintf(stderr, \"Auto-update Failed: no temporary file available\\n\");\n\t\tfree((void*)(tmpdir));\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tFILE *fp = _wfopen(tmpf, L\"wb\");\n\tif (NULL == fp) {\n\t\tfprintf(stderr, \"Auto-update Failed: cannot open temporary file %S\\n\", tmpf);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tfprintf(stderr, \" to %S\\n\", tmpf);\n\tsize_t fwrite_ret = fwrite(uncomp, sizeof(char), strm.total_out, fp);\n\tif (fwrite_ret != strm.total_out) {\n\t\tfprintf(stderr, \"Auto-update Failed: fwrite failed %S\\n\", tmpf);\n\t\tfclose(fp);\n\t\tDeleteFileW(tmpf);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tfclose(fp);\n\tfree(uncomp);\n\tfree(body_buffer);\n\t// Backup\n\twchar_t *selftmpf = autoupdate_tmpf(tmpdir, \"exe\");\n\tif (NULL == selftmpf) {\n\t\tfprintf(stderr, \"Auto-update Failed: no temporary file available\\n\");\n\t\tDeleteFileW(tmpf);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\treturn 2;\n\t}\n\tfprintf(stderr, \"Moving the old version from %S to %S\\n\", exec_path, selftmpf);\n\tBOOL ret = MoveFileExW(exec_path, selftmpf, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH);\n\tif (!ret) {\n\t\tfprintf(stderr, \"Auto-update Failed: MoveFileW failed with GetLastError=%d\\n\", GetLastError());\n\t\tDeleteFileW(tmpf);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tfree((void*)(selftmpf));\n\t\treturn 2;\n\t}\n\t// Move the new version into the original place\n\tfprintf(stderr, \"Moving the new version from %S to %S \\n\", tmpf, exec_path);\n\tret = MoveFileExW(tmpf, exec_path, MOVEFILE_COPY_ALLOWED | MOVEFILE_WRITE_THROUGH);\n\tif (!ret) {\n\t\tfprintf(stderr, \"Auto-update Failed: MoveFileW failed with GetLastError=%d\\n\", GetLastError());\n\t\tDeleteFileW(tmpf);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tfree((void*)(selftmpf));\n\t\treturn 2;\n\t}\n\t// Restarting\n\tfprintf(stderr, \"Restarting...\\n\");\n\tfflush(stderr);\n\tSTARTUPINFO si;\n\tPROCESS_INFORMATION pi;\n\tZeroMemory(&si, sizeof(si));\n\tsi.cb = sizeof(si);\n\tZeroMemory(&pi, sizeof(pi));\n\tret = CreateProcess(\n\t\tNULL,\t     // No module name (use command line)\n\t\tGetCommandLine(), // Command line\n\t\tNULL,\t     // Process handle not inheritable\n\t\tNULL,\t     // Thread handle not inheritable\n\t\tFALSE,\t    // Set handle inheritance to FALSE\n\t\t0,\t\t// No creation flags\n\t\tNULL,\t     // Use parent's environment block\n\t\tNULL,\t     // Use parent's starting directory \n\t\t&si,\t      // Pointer to STARTUPINFO structure\n\t\t&pi\t       // Pointer to PROCESS_INFORMATION structure\n\t);\n\tif (!ret) {\n\t\tfprintf(stderr, \"Auto-update Failed: CreateProcess failed with GetLastError=%d\\n\", GetLastError());\n\t\tDeleteFileW(tmpf);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tfree((void*)(selftmpf));\n\t\treturn 3;\n\t}\n\t// Wait until child process exits.\n\tWaitForSingleObject(pi.hProcess, INFINITE);\n\t// Close process and thread handles. \n\tCloseHandle(pi.hProcess);\n\tCloseHandle(pi.hThread);\n\tfprintf(stderr, \"Deleting %S\\n\", selftmpf);\n\tfflush(stderr);\n\t_wexeclp(L\"cmd\", L\"cmd\", L\"/c\", L\"ping\", L\"127.0.0.1\", L\"-n\", L\"3\", L\">nul\", L\"&\", L\"del\", selftmpf, NULL);\n\t// we should never reach here\n\tassert(0);\n\treturn 3;\n}\n\n#else\n\n#include <assert.h>\n#include <stdio.h> /* printf, sprintf */\n#include <stdlib.h> /* exit */\n#include <unistd.h> /* read, write, close */\n#include <string.h> /* memcpy, memset */\n#include <sys/socket.h> /* socket, connect */\n#include <netinet/in.h> /* struct sockaddr_in, struct sockaddr */\n#include <netdb.h> /* struct hostent, gethostbyname */\n#include <unistd.h>\n#include <sys/select.h>\n#include <limits.h>  /* PATH_MAX */\n#include <sys/stat.h> /* struct stat */\n#include <errno.h>\n\nint autoupdate(\n\tint argc,\n\tchar *argv[],\n\tconst char *host,\n\tuint16_t port,\n\tconst char *path,\n\tconst char *current,\n\tshort force\n)\n{\n\tstruct hostent *server;\n\tstruct sockaddr_in serv_addr;\n\tint sockfd, bytes, total;\n\tchar response[1024 * 10 + 1]; // 10KB\n\n\tif (!force && !autoupdate_should_proceed()) {\n\t\treturn 1;\n\t}\n\n\tif (!force && !autoupdate_should_proceed_24_hours(argc, argv, 0)) {\n\t\treturn 4;\n\t}\n\n\tsockfd = socket(AF_INET, SOCK_STREAM, 0);\n\tif (sockfd < 0) {\n\t\tfprintf(stderr, \"Auto-update Failed: socket creation failed\\n\");\n\t\treturn 2;\n\t}\n\tserver = gethostbyname(host);\n\tif (server == NULL) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: gethostbyname failed for %s\\n\", host);\n\t\treturn 2;\n\t}\n\tmemset(&serv_addr, 0, sizeof(serv_addr));\n\tserv_addr.sin_family = AF_INET;\n\tserv_addr.sin_port = htons(port);\n\tmemcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);\n\tif (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: connect failed on %s and port %d\\n\", host, port);\n\t\treturn 2;\n\t}\n\tif (5 != write(sockfd, \"HEAD \", 5) ||\n\t\tstrlen(path) != write(sockfd, path, strlen(path)) ||\n\t\t11 != write(sockfd, \" HTTP/1.0\\r\\n\", 11) ||\n\t\t6 != write(sockfd, \"Host: \", 6) ||\n\t\tstrlen(host) != write(sockfd, host, strlen(host)) ||\n\t\t4 != write(sockfd, \"\\r\\n\\r\\n\", 4)) {\n\t\t\tclose(sockfd);\n\t\t\tfprintf(stderr, \"Auto-update Failed: write failed\\n\");\n\t\t\treturn 2;\n\t}\n\ttotal = sizeof(response) - 2;\n\tlong long received = 0;\n\tdo {\n\t\tbytes = read(sockfd, response + received, total - received);\n\t\tif (bytes < 0) {\n\t\t\tclose(sockfd);\n\t\t\tfprintf(stderr, \"Auto-update Failed: read failed\\n\");\n\t\t\treturn 2;\n\t\t}\n\t\tif (bytes == 0) {\n\t\t\t/* EOF */\n\t\t\t*(response + received) = 0;\n\t\t\tbreak;\n\t\t}\n\t\treceived += bytes;\n\t} while (received < total);\n\tif (received == total) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: read causes buffer full\\n\");\n\t\treturn 2;\n\t}\n\tclose(sockfd);\n\tassert(received < total);\n\tsize_t len = strlen(response);\n\tshort again_302 = 0;\nparse_location_header:\n\tassert(len <= total);\n\tchar *new_line = NULL;\n\tchar *found = NULL;\n\tsize_t i = 0;\n\tresponse[sizeof(response) - 1] = 0;\n\twhile (i < len) {\n\t\tnew_line = strstr(response + i, \"\\r\\n\");\n\t\tif (NULL == new_line) {\n\t\t\tbreak;\n\t\t}\n\t\t*new_line = 0;\n\t\tif (0 == strncmp(response + i, \"Location: \", 10)) {\n\t\t\tfound = response + i + 10;\n\t\t\tbreak;\n\t\t}\n\t\t*new_line = '\\r';\n\t\ti = new_line - response + 2;\n\t}\n\tif (!found) {\n\t\tfprintf(stderr, \"Auto-update Failed: failed to find a Location header\\n\");\n\t\treturn 2;\n\t}\n\tif (!again_302) {\n\t\tif (strstr(found, current)) {\n\t\t\t/* Latest version confirmed. No need to update */\n\t\t\tautoupdate_should_proceed_24_hours(argc, argv, 1);\n\t\t\treturn 0;\n\t\t} else {\n\t\t\tfprintf(stderr, \"Hint: to disable auto-update, run with environment variable CI=true\\n\");\n\t\t\tfflush(stderr);\n\t\t}\n\t}\n\n\tchar *url = found;\n\tfprintf(stderr, \"Downloading update from %s\\n\", url);\n\tfflush(stderr);\n\n\tchar *host2;\n\tuint16_t port2 = 80;\n\tif (strlen(url) >= 8 && 0 == strncmp(\"https://\", url, 8)) {\n\t\thost2 = url + 8;\n\t} else if (strlen(url) >= 7 && 0 == strncmp(\"http://\", url, 7)) {\n\t\thost2 = url + 7;\n\t} else {\n\t\tfprintf(stderr, \"Auto-update Failed: failed to find http:// or https:// at the beginning of URL %s\\n\", url);\n\t\treturn 2;\n\t}\n\tchar *found_slash = strchr(host2, '/');\n\tchar *request_path;\n\tif (NULL == found_slash) {\n\t\trequest_path = \"/\";\n\t} else {\n\t\trequest_path = found_slash;\n\t\t*found_slash = 0;\n\t}\n\tsockfd = socket(AF_INET, SOCK_STREAM, 0);\n\tif (sockfd < 0) {\n\t\tfprintf(stderr, \"Auto-update Failed: socket creation failed\\n\");\n\t\treturn 2;\n\t}\n\tserver = gethostbyname(host2);\n\tif (server == NULL) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: gethostbyname failed for %s\\n\", host2);\n\t\treturn 2;\n\t}\n\tmemset(&serv_addr, 0, sizeof(serv_addr));\n\tserv_addr.sin_family = AF_INET;\n\tserv_addr.sin_port = htons(port2);\n\tmemcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);\n\tif (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: connect failed on %s and port %d\\n\", host2, port2);\n\t\treturn 2;\n\t}\n\tif (NULL != found_slash) {\n\t\t*found_slash = '/';\n\t}\n\tif (4 != write(sockfd, \"GET \", 4) ||\n\t\tstrlen(request_path) != write(sockfd, request_path, strlen(request_path)) ||\n\t\t11 != write(sockfd, \" HTTP/1.0\\r\\n\", 11)) {\n\t\t\tclose(sockfd);\n\t\t\tfprintf(stderr, \"Auto-update Failed: write failed\\n\");\n\t\t\treturn 2;\n\t}\n\tif (NULL != found_slash) {\n\t\t*found_slash = 0;\n\t}\n\tif (6 != write(sockfd, \"Host: \", 6) ||\n\t\tstrlen(host2) != write(sockfd, host2, strlen(host2)) ||\n\t\t4 != write(sockfd, \"\\r\\n\\r\\n\", 4)) {\n\t\t\tclose(sockfd);\n\t\t\tfprintf(stderr, \"Auto-update Failed: write failed\\n\");\n\t\t\treturn 2;\n\t}\n\n\t// Read the header\n\ttotal = sizeof(response) - 2;\n\tresponse[sizeof(response) - 1] = 0;\n\treceived = 0;\n\tchar *header_end = NULL;\n\tdo {\n\t\tbytes = read(sockfd, response + received, total - received);\n\t\tif (bytes < 0) {\n\t\t\tclose(sockfd);\n\t\t\tfprintf(stderr, \"Auto-update Failed: read failed\\n\");\n\t\t\treturn 2;\n\t\t}\n\t\tif (bytes == 0) {\n\t\t\t/* EOF */\n\t\t\t*(response + received) = 0;\n\t\t\tbreak;\n\t\t}\n\t\t*(response + received + bytes) = 0;\n\t\theader_end = strstr(response + received, \"\\r\\n\\r\\n\");\n\t\treceived += bytes;\n\t\tif (header_end) {\n\t\t\tbreak;\n\t\t}\n\t} while (received < total);\n\tif (NULL == header_end) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: failed to find the end of the response header\\n\");\n\t\treturn 2;\n\t}\n\tassert(received <= total);\n\n\t// Possible new 302\n\tif (received > 13 && (\n\t\t0 == strncmp(response, \"HTTP/1.1 302 \", 13) ||\n\t\t0 == strncmp(response, \"HTTP/1.0 302 \", 13))) {\n\t\t\tlen = received;\n\t\t\tagain_302 = 1;\n\t\t\tgoto parse_location_header;\n\t}\n\n\t// Parse the header\n\tlen = received;\n\tassert(len <= total);\n\tnew_line = NULL;\n\tlong long found_length = -1;\n\ti = 0;\n\tresponse[sizeof(response) - 1] = 0;\n\twhile (i < len) {\n\t\tnew_line = strstr(response + i, \"\\r\\n\");\n\t\tif (NULL == new_line) {\n\t\t\tbreak;\n\t\t}\n\t\t*new_line = 0;\n\t\tif (0 == strncmp(response + i, \"Content-Length: \", 16)) {\n\t\t\tfound_length = atoll(response + i + 16);\n\t\t\tbreak;\n\t\t}\n\t\t*new_line = '\\r';\n\t\ti = new_line - response + 2;\n\t}\n\tif (-1 == found_length) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: failed to find a Content-Length header\\n\");\n\t\treturn 2;\n\t}\n\tif (0 == found_length) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: found a Content-Length header of zero\\n\");\n\t\treturn 2;\n\t}\n\tassert(found_length > 0);\n\t// Read the body\n\t// header_end -> \\r\\n\\r\\n\n\tassert(header_end);\n\tassert(header_end + 4 <= response + received);\n\t// put the rest of over-read content when reading header\n\tsize_t the_rest = response + received - (header_end + 4);\n\tchar *body_buffer = (char *)(malloc(found_length));\n\tif (NULL == body_buffer) {\n\t\tclose(sockfd);\n\t\tfprintf(stderr, \"Auto-update Failed: Insufficient memory\\n\");\n\t\treturn 2;\n\t}\n\tmemcpy(body_buffer, (header_end + 4), the_rest);\n\tchar *body_buffer_ptr = body_buffer + the_rest;\n\tchar *body_buffer_end = body_buffer + found_length;\n\t// read the remaining body\n\treceived = the_rest;\n\tfprintf(stderr, \"\\r%lld / %lld bytes finished (%lld%%)\",  received, found_length, received*100LL/found_length);\n\tfflush(stderr);\n\twhile (received < found_length) {\n\t\tsize_t space = 100 * 1024;\n\t\tif (space > body_buffer_end - body_buffer_ptr) {\n\t\t\tspace = body_buffer_end - body_buffer_ptr;\n\t\t}\n\t\tbytes = read(sockfd, body_buffer_ptr, space);\n\t\tif (bytes < 0) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: read failed\\n\");\n\t\t\tfree(body_buffer);\n\t\t\tclose(sockfd);\n\t\t\treturn 2;\n\t\t}\n\t\tif (bytes == 0) {\n\t\t\t/* EOF */\n\t\t\tbreak;\n\t\t}\n\t\treceived += bytes;\n\t\tbody_buffer_ptr += bytes;\n\t\tfprintf(stderr, \"\\r%lld / %lld bytes finished (%lld%%)\",  received, found_length, received*100LL/found_length);\n\t\tfflush(stderr);\n\t}\n\tif (received != found_length) {\n\t\tassert(received < found_length);\n\t\tfprintf(stderr, \"Auto-update Failed: prematurely reached EOF after reading %lld bytes\\n\", received);\n\t\tclose(sockfd);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tfprintf(stderr, \"\\n\");\n\tfflush(stderr);\n\tclose(sockfd);\n\t// Inflate to a file\n\tfprintf(stderr, \"Inflating\");\n\tfflush(stderr);\n\tunsigned full_length = found_length;\n\tunsigned half_length = found_length / 2;\n\tunsigned uncompLength = full_length;\n\tchar* uncomp = (char*) calloc( sizeof(char), uncompLength );\n\tif (NULL == uncomp) {\n\t\tfprintf(stderr, \"Auto-update Failed: Insufficient memory\\n\");\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\n\tz_stream strm;\n\tstrm.next_in = (Bytef *)body_buffer;\n\tstrm.avail_in = found_length;\n\tstrm.total_out = 0;\n\tstrm.zalloc = Z_NULL;\n\tstrm.zfree = Z_NULL;\n\t\n\tshort done = 0;\n\n\tif (inflateInit2(&strm, (16+MAX_WBITS)) != Z_OK) {\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\tfprintf(stderr, \"Auto-update Failed: inflateInit2 failed\\n\");\n\t\treturn 2;\n\t}\n\t\n\twhile (!done) {\n\t\t// If our output buffer is too small\n\t\tif (strm.total_out >= uncompLength ) {\n\t\t\t// Increase size of output buffer\n\t\t\tchar* uncomp2 = (char*) calloc( sizeof(char), uncompLength + half_length );\n\t\t\tif (NULL == uncomp2) {\n\t\t\t\tfree(uncomp);\n\t\t\t\tfree(body_buffer);\n\t\t\t\tfprintf(stderr, \"Auto-update Failed: calloc failed\\n\");\n\t\t\t\treturn 2;\n\t\t\t}\n\t\t\tmemcpy( uncomp2, uncomp, uncompLength );\n\t\t\tuncompLength += half_length ;\n\t\t\tfree( uncomp );\n\t\t\tuncomp = uncomp2 ;\n\t\t}\n\t\t\n\t\tstrm.next_out = (Bytef *) (uncomp + strm.total_out);\n\t\tstrm.avail_out = uncompLength - strm.total_out;\n\t\t\n\t\t// Inflate another chunk.\n\t\tint err = inflate(&strm, Z_SYNC_FLUSH);\n\t\tif (err == Z_STREAM_END) {\n\t\t\tdone = 1;\n\t\t}\n\t\telse if (err != Z_OK)  {\n\t\t\tfprintf(stderr, \"Auto-update Failed: inflate failed with %d\\n\", err);\n\t\t\tfree(uncomp);\n\t\t\tfree(body_buffer);\n\t\t\treturn 2;\n\t\t}\n\t}\n\n\tif (inflateEnd (&strm) != Z_OK) {\n\t\tfprintf(stderr, \"Auto-update Failed: inflateInit2 failed\\n\");\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\n\tchar *tmpdir = autoupdate_tmpdir();\n\tif (NULL == tmpdir) {\n\t\tfprintf(stderr, \"Auto-update Failed: no temporary folder found\\n\");\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tchar *tmpf = autoupdate_tmpf(tmpdir, NULL);\n\tif (NULL == tmpf) {\n\t\tfprintf(stderr, \"Auto-update Failed: no temporary file available\\n\");\n\t\tfree((void*)(tmpdir));\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tFILE *fp = fopen(tmpf, \"wb\");\n\tif (NULL == fp) {\n\t\tfprintf(stderr, \"Auto-update Failed: cannot open temporary file %s\\n\", tmpf);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tfprintf(stderr, \" to %s\\n\", tmpf);\n\tsize_t fwrite_ret = fwrite(uncomp, sizeof(char), strm.total_out, fp);\n\tif (fwrite_ret != strm.total_out) {\n\t\tfprintf(stderr, \"Auto-update Failed: fwrite failed %s\\n\", tmpf);\n\t\tfclose(fp);\n\t\tunlink(tmpf);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tfree(uncomp);\n\t\tfree(body_buffer);\n\t\treturn 2;\n\t}\n\tfclose(fp);\n\tfree(uncomp);\n\tfree(body_buffer);\n\t// chmod\n\tsize_t exec_path_len = 2 * PATH_MAX;\n\tchar* exec_path = (char*)(malloc(exec_path_len));\n\tif (NULL == exec_path) {\n\t\tfprintf(stderr, \"Auto-update Failed: Insufficient memory allocating exec_path\\n\");\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tunlink(tmpf);\n\t\treturn 2;\n\t}\n\tif (autoupdate_exepath(exec_path, &exec_path_len) != 0) {\n\t\tif (!argv[0]) {\n\t\t\tfprintf(stderr, \"Auto-update Failed: missing argv[0]\\n\");\n\t\t\tfree((void*)(tmpdir));\n\t\t\tfree((void*)(tmpf));\n\t\t\tunlink(tmpf);\n\t\t\treturn 2;\n\t\t}\n\t\tassert(strlen(argv[0]) < 2 * PATH_MAX);\n\t\tmemcpy(exec_path, argv[0], strlen(argv[0]));\n\t}\n\tstruct stat current_st;\n\tint ret = stat(exec_path, &current_st);\n\tif (0 != ret) {\n\t\tfprintf(stderr, \"Auto-update Failed: stat failed for %s\\n\", exec_path);\n\t\tfree(exec_path);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tunlink(tmpf);\n\t\treturn 2;\n\t}\n\tret = chmod(tmpf, current_st.st_mode | S_IXUSR);\n\tif (0 != ret) {\n\t\tfprintf(stderr, \"Auto-update Failed: chmod failed for %s\\n\", tmpf);\n\t\tfree(exec_path);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tunlink(tmpf);\n\t\treturn 2;\n\t}\n\t// Move the new version into the original place\n\tfprintf(stderr, \"Moving the new version from %s to %s\\n\", tmpf, exec_path);\n\tret = rename(tmpf, exec_path);\n\tif (0 != ret) {\n\t\tfprintf(stderr, \"Auto-update Failed: failed calling rename %s to %s\\n\", tmpf, exec_path);\n\t\tfree(exec_path);\n\t\tfree((void*)(tmpdir));\n\t\tfree((void*)(tmpf));\n\t\tunlink(tmpf);\n\t\treturn 2;\n\t}\n\tfprintf(stderr, \"Restarting...\\n\");\n\tret = execv(exec_path, argv);\n\t// we should not reach this point\n\tfprintf(stderr, \"Auto-update Failed: execv failed with %d (errno %d)\\n\", ret, errno);\n\tfree(exec_path);\n\tfree((void*)(tmpdir));\n\tfree((void*)(tmpf));\n\tunlink(tmpf);\n\treturn 3;\n}\n\n#endif // _WIN32\n"
  },
  {
    "path": "src/autoupdate_internal.h",
    "content": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n * For full terms see the included LICENSE file\n */\n\n#ifndef AUTOUPDATE_INTERNAL_H_A40E122A\n#define AUTOUPDATE_INTERNAL_H_A40E122A\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef _WIN32\n\n#define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )\n\nPACK(\n\tstruct ZIPLocalFileHeader\n{\n\tuint32_t signature;\n\tuint16_t versionNeededToExtract; // unsupported\n\tuint16_t generalPurposeBitFlag; // unsupported\n\tuint16_t compressionMethod;\n\tuint16_t lastModFileTime;\n\tuint16_t lastModFileDate;\n\tuint32_t crc32;\n\tuint32_t compressedSize;\n\tuint32_t uncompressedSize;\n\tuint16_t fileNameLength;\n\tuint16_t extraFieldLength; // unsupported\n});\n\nwchar_t* autoupdate_tmpdir();\nwchar_t* autoupdate_tmpf(wchar_t *tmpdir, const char *ext_name);\nshort autoupdate_should_proceed_24_hours(int argc, wchar_t *wargv[], short will_write);\n\n#else\n\nchar* autoupdate_tmpdir();\nchar* autoupdate_tmpf(char *tmpdir, const char *ext_name);\nshort autoupdate_should_proceed_24_hours(int argc, char *argv[], short will_write);\n\t\n#endif // _WIN32\n\nshort autoupdate_should_proceed();\nint autoupdate_exepath(char* buffer, size_t* size);\n\n#endif /* end of include guard: AUTOUPDATE_INTERNAL_H_A40E122A */\n"
  },
  {
    "path": "src/exepath.c",
    "content": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n * For full terms see the included LICENSE file\n */\n\n/*\n * autoupdate_exepath is derived from uv_exepath of libuv.\n * libuv is licensed for use as follows:\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n * \n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n * \n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\n#include \"autoupdate.h\"\n#include \"autoupdate_internal.h\"\n\n#ifdef _WIN32\n\n#include <assert.h>\n#include <direct.h>\n#include <limits.h>\n#include <stdio.h>\n#include <string.h>\n#include <time.h>\n#include <wchar.h>\n#include <windows.h>\n\nint autoupdate_exepath(char* buffer, size_t* size_ptr) {\n  int utf8_len, utf16_buffer_len, utf16_len;\n  WCHAR* utf16_buffer;\n  int err;\n\n  if (buffer == NULL || size_ptr == NULL || *size_ptr == 0) {\n    return -1;\n  }\n\n  if (*size_ptr > 32768) {\n    /* Windows paths can never be longer than this. */\n    utf16_buffer_len = 32768;\n  } else {\n    utf16_buffer_len = (int) *size_ptr;\n  }\n\n  utf16_buffer = (WCHAR*) malloc(sizeof(WCHAR) * utf16_buffer_len);\n  if (!utf16_buffer) {\n    return -1;\n  }\n\n  /* Get the path as UTF-16. */\n  utf16_len = GetModuleFileNameW(NULL, utf16_buffer, utf16_buffer_len);\n  if (utf16_len <= 0) {\n    err = GetLastError();\n    goto error;\n  }\n\n  /* utf16_len contains the length, *not* including the terminating null. */\n  utf16_buffer[utf16_len] = L'\\0';\n\n  /* Convert to UTF-8 */\n  utf8_len = WideCharToMultiByte(CP_UTF8,\n                                 0,\n                                 utf16_buffer,\n                                 -1,\n                                 buffer,\n                                 (int) *size_ptr,\n                                 NULL,\n                                 NULL);\n  if (utf8_len == 0) {\n    err = GetLastError();\n    goto error;\n  }\n\n  free(utf16_buffer);\n\n  /* utf8_len *does* include the terminating null at this point, but the */\n  /* returned size shouldn't. */\n  *size_ptr = utf8_len - 1;\n  return 0;\n\n error:\n  free(utf16_buffer);\n  return -1;\n}\n\n#endif\n\n#ifdef __linux__\n#include <unistd.h>\n\nint autoupdate_exepath(char* buffer, size_t* size) {\n  ssize_t n;\n\n  if (buffer == NULL || size == NULL || *size == 0)\n    return -1;\n\n  n = *size - 1;\n  if (n > 0)\n    n = readlink(\"/proc/self/exe\", buffer, n);\n\n  if (n == -1)\n    return -1;\n\n  buffer[n] = '\\0';\n  *size = n;\n\n  return 0;\n}\n#endif\n\n#ifdef __APPLE__\n#include <unistd.h>\n#include <string.h>\n#include <stdlib.h>\n#include <mach-o/dyld.h>\n#include <limits.h>  // PATH_MAX\n\nint autoupdate_exepath(char* buffer, size_t* size) {\n  /* realpath(exepath) may be > PATH_MAX so double it to be on the safe side. */\n  char abspath[PATH_MAX * 2 + 1];\n  char exepath[PATH_MAX + 1];\n  uint32_t exepath_size;\n  size_t abspath_size;\n\n  if (buffer == NULL || size == NULL || *size == 0)\n    return -1;\n\n  exepath_size = sizeof(exepath);\n  if (_NSGetExecutablePath(exepath, &exepath_size))\n    return -1;\n\n  if (realpath(exepath, abspath) != abspath)\n    return -1;\n\n  abspath_size = strlen(abspath);\n  if (abspath_size == 0)\n    return -1;\n\n  *size -= 1;\n  if (*size > abspath_size)\n    *size = abspath_size;\n\n  memcpy(buffer, abspath, *size);\n  buffer[*size] = '\\0';\n\n  return 0;\n}\n#endif\n"
  },
  {
    "path": "src/inflate.c",
    "content": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n * For full terms see the included LICENSE file\n */\n\n#include \"autoupdate.h\"\n#include \"autoupdate_internal.h\"\n"
  },
  {
    "path": "src/tmpf.c",
    "content": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n * For full terms see the included LICENSE file\n */\n\n#include \"autoupdate.h\"\n#include \"autoupdate_internal.h\"\n\n#include <time.h>\n#include <stdlib.h>\n\n#ifdef _WIN32\n\n#include <Windows.h>\n#include <Shlwapi.h>\n#include <process.h>\n#include <assert.h>\n\nwchar_t* autoupdate_tmpdir()\n{\n\tconst int squash_win32_buf_sz = 32767;\n\twchar_t squash_win32_buf[32767 + 1];\n\tDWORD length;\n\n\tlength = GetEnvironmentVariableW(L\"TEMP\", squash_win32_buf, squash_win32_buf_sz);\n\tif (length) {\n\t\tgoto out;\n\t}\n\tlength = GetEnvironmentVariableW(L\"TMP\", squash_win32_buf, squash_win32_buf_sz);\n\tif (length) {\n\t\tgoto out;\n\t}\n\tlength = GetEnvironmentVariableW(L\"SystemRoot\", squash_win32_buf, squash_win32_buf_sz);\n\tif (!length) {\n\t\tlength = GetEnvironmentVariableW(L\"windir\", squash_win32_buf, squash_win32_buf_sz);\n\t}\n\tif (length) {\n\t\tif (length + 5 >= squash_win32_buf_sz) {\n\t\t\treturn NULL;\n\t\t}\n\t\tsquash_win32_buf[length] = L'\\\\';\n\t\tsquash_win32_buf[length + 1] = L't';\n\t\tsquash_win32_buf[length + 2] = L'e';\n\t\tsquash_win32_buf[length + 3] = L'm';\n\t\tsquash_win32_buf[length + 4] = L'p';\n\t\tsquash_win32_buf[length + 5] = 0;\n\t\tlength += 5;\n\t\tgoto out;\n\t}\n\treturn NULL;\nout:\n\tif (length >= 2 && L'\\\\' == squash_win32_buf[length - 1] && L':' != squash_win32_buf[length - 2]) {\n\t\tsquash_win32_buf[length - 1] = 0;\n\t\tlength -= 1;\n\t}\n\treturn wcsdup(squash_win32_buf);\n}\n\nwchar_t* autoupdate_tmpf(wchar_t *tmpdir, const char *ext_name)\n{\n\tconst int squash_win32_buf_sz = 32767;\n\twchar_t squash_win32_buf[32767 + 1];\n\tsize_t curlen, size_ret;\n\tint try_cnt = 0;\n\tsrand(time(NULL) * getpid());\n\tsquash_win32_buf[squash_win32_buf_sz] = 0;\n\twhile (try_cnt < 3) {\n\t\tsquash_win32_buf[0] = 0;\n\t\tassert(0 == wcslen(squash_win32_buf));\n\t\twcsncat(squash_win32_buf + wcslen(squash_win32_buf), tmpdir, squash_win32_buf_sz - wcslen(squash_win32_buf));\n\t\twcsncat(squash_win32_buf + wcslen(squash_win32_buf), L\"\\\\libautoupdate-\", squash_win32_buf_sz - wcslen(squash_win32_buf));\n\t\t// up to 33 characters for _itoa\n\t\tif (squash_win32_buf_sz - wcslen(squash_win32_buf) <= 33) {\n\t\t\treturn NULL;\n\t\t}\n\t\t_itow(rand(), squash_win32_buf + wcslen(squash_win32_buf), 10);\n\t\tif (ext_name) {\n\t\t\twcsncat(squash_win32_buf + wcslen(squash_win32_buf), L\".\", squash_win32_buf_sz - wcslen(squash_win32_buf));\n\t\t}\n\t\tif (ext_name) {\n\t\t\tcurlen = wcslen(squash_win32_buf);\n\t\t\tsize_ret = mbstowcs((wchar_t*)(squash_win32_buf) + curlen, ext_name, squash_win32_buf_sz - curlen);\n\t\t\tif ((size_t)-1 == size_ret) {\n\t\t\t\treturn NULL;\n\t\t\t}\n\t\t\t*((wchar_t*)(squash_win32_buf) + curlen + size_ret) = 0;\n\t\t}\n\t\tif (!PathFileExistsW(squash_win32_buf)) {\n\t\t\treturn wcsdup(squash_win32_buf);\n\t\t}\n\t\t++try_cnt;\n\t}\n\treturn NULL;\n}\n\n#else\n\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <string.h>\n#include <stdio.h>\n\nchar* autoupdate_tmpdir()\n{\n\tchar *try_try;\n\tsize_t length;\n\ttry_try = getenv(\"TMPDIR\");\n\tif (try_try) {\n\t\tgoto out;\n\t}\n\ttry_try = getenv(\"TMP\");\n\tif (try_try) {\n\t\tgoto out;\n\t}\n\ttry_try = getenv(\"TEMP\");\n\tif (try_try) {\n\t\tgoto out;\n\t}\n\ttry_try = \"/tmp\";\nout:\n\ttry_try = strdup(try_try);\n\tlength = strlen(try_try);\n\tif (length >= 2 && '/' == try_try[length - 1]) {\n\t\ttry_try[length - 1] = 0;\n\t}\n\treturn try_try;\n}\n\nchar* autoupdate_tmpf(char *tmpdir, const char *ext_name)\n{\n\tconst int squash_buf_sz = 32767;\n\tchar squash_buf[squash_buf_sz + 1];\n\tint ret, try_cnt = 0;\n\tstruct stat statbuf;\n\n\tsrand(time(NULL) * getpid());\n\twhile (try_cnt < 3) {\n\t\tif (ext_name) {\n\t\t\tret = snprintf(squash_buf, squash_buf_sz, \"%s/libautoupdate-%d.%s\", tmpdir, rand(), ext_name);\n\t\t} else {\n\t\t\tret = snprintf(squash_buf, squash_buf_sz, \"%s/libautoupdate-%d\", tmpdir, rand());\n\t\t}\n\t\tif (-1 == ret) {\n\t\t\treturn NULL;\n\t\t}\n\t\tif (-1 == stat(squash_buf, &statbuf)) {\n\t\t\treturn strdup(squash_buf);\n\t\t}\n\t\t++try_cnt;\n\t}\n\treturn NULL;\n}\n\n#endif // _WIN32\n"
  },
  {
    "path": "src/utils.c",
    "content": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n * For full terms see the included LICENSE file\n */\n\n#include \"autoupdate.h\"\n#include \"autoupdate_internal.h\"\n#include <assert.h>\n\n#ifdef _WIN32\n\n#include <Windows.h>\n#include <wchar.h>\n#include <Shlobj.h>\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <string.h>\n#include <time.h>\n#include <limits.h>\n\nshort autoupdate_should_proceed()\n{\n\tTCHAR lpBuffer[32767 + 1];\n\tif (0 == GetEnvironmentVariable(\"CI\", lpBuffer, 32767)) {\n\t\treturn 1;\n\t} else {\n\t\treturn 0;\n\t}\n}\n\n#else\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <pwd.h>\n#include <string.h>\n#include <time.h>\n#include <limits.h>\n\nshort autoupdate_should_proceed()\n{\n\tif (NULL == getenv(\"CI\")) {\n\t\treturn 1;\n\t} else {\n\t\treturn 0;\n\t}\n}\n\n#endif // _WIN32\n\n#ifdef _WIN32\nshort autoupdate_should_proceed_24_hours(int argc, wchar_t *wargv[], short will_write)\n{\n\tconst KNOWNFOLDERID rfid = FOLDERID_Profile;\n\tPWSTR ppszPath = NULL;\n\tHRESULT hret;\n\twchar_t filepath[2 * 32768];\n\tconst wchar_t *filename = L\"\\\\.libautoupdate\";\n\tsize_t exec_path_len = 2 * 32768;\n\tchar exec_path[2 * 32768];\n#else\nshort autoupdate_should_proceed_24_hours(int argc, char *argv[], short will_write)\n{\n\tchar *filepath = NULL;\n\tconst char *filename = \"/.libautoupdate\";\n\tsize_t exec_path_len = 2 * PATH_MAX;\n\tchar exec_path[2 * PATH_MAX];\n\tstruct passwd *pw;\n\tconst char *homedir;\n#endif // _WIN32\n\tshort has_written = 0;\n\ttime_t time_now;\n\tlong item_time;\n\tchar *item_string = NULL;\n\tchar *item_space;\n\tchar *cursor;\n\tchar *string = NULL;\n\tchar *string0 = NULL;\n\tlong fsize;\n\tFILE *f = NULL;\n\tint ret;\n\tsize_t size_t_ret;\n\t\n\tif (autoupdate_exepath(exec_path, &exec_path_len) != 0) {\n#ifdef _WIN32\n\t\tgoto exit;\n#else\n\t\tif (!argv[0]) {\n\t\t\tgoto exit;\n\t\t}\n\t\tassert(strlen(argv[0]) < 2 * PATH_MAX);\n\t\tmemcpy(exec_path, argv[0], strlen(argv[0]));\n#endif\n\t}\n\n\ttime_now = time(NULL);\n\tif ((time_t)-1 == time_now) {\n\t\tgoto exit;\n\t}\n#ifdef _WIN32\n\thret = SHGetKnownFolderPath(\n\t\t&rfid,\n\t\t0,\n\t\tNULL,\n\t\t&ppszPath\n\t);\n\tif (S_OK != hret) {\n\t\tgoto exit;\n\t}\n\tmemcpy(filepath, ppszPath, wcslen(ppszPath) * sizeof(wchar_t));\n\tmemcpy(filepath + wcslen(ppszPath), filename, wcslen(filename) * sizeof(wchar_t));\n\tfilepath[wcslen(ppszPath) + wcslen(filename)] = 0;\n\tf = _wfopen(filepath, L\"rb\");\n#else\n\tpw = getpwuid(getuid());\n\tif (NULL == pw) {\n\t\tgoto exit;\n\t}\n\thomedir = pw->pw_dir;\n\tif (NULL == homedir) {\n\t\tgoto exit;\n\t}\n\tfilepath = malloc(strlen(homedir) + strlen(filename) + 1);\n\tif (NULL == filepath) {\n\t\tgoto exit;\n\t}\n\tmemcpy(filepath, homedir, strlen(homedir));\n\tmemcpy(filepath + strlen(homedir), filename, strlen(filename));\n\tfilepath[strlen(homedir) + strlen(filename)] = 0;\n\tf = fopen(filepath, \"rb\");\n#endif // _WIN32\n\tif (NULL == f) {\n\t\tif (will_write) {\n\t\t\tstring0 = NULL;\n\t\t\tgoto write;\n\t\t} else {\n\t\t\tgoto exit;\n\t\t}\n\t}\n\tret = fseek(f, 0, SEEK_END);\n\tif (0 != ret) {\n\t\tgoto exit;\n\t}\n\tfsize = ftell(f);\n\tif (fsize <= 0) {\n\t\tgoto exit;\n\t}\n\tret = fseek(f, 0, SEEK_SET);\n\tif (0 != ret) {\n\t\tgoto exit;\n\t}\n\t\n\tstring = malloc(fsize + 1);\n\tif (NULL == string) {\n\t\tgoto exit;\n\t}\n\tstring0 = string;\n\tsize_t_ret = fread(string, fsize, 1, f);\n\tif (1 != size_t_ret) {\n\t\tgoto exit;\n\t}\n\tstring[fsize] = 0;\n\tret = fclose(f);\n\tif (0 != ret) {\n\t\tgoto exit;\n\t}\n\tf = NULL;\n\tstring[fsize] = 0;\n\twhile (string < string0 + fsize) {\n\t\tcursor = strchr(string, '\\n');\n\t\tif (!cursor) {\n\t\t\tif (will_write) {\n\t\t\t\tstring0 = NULL;\n\t\t\t\tgoto write;\n\t\t\t} else {\n\t\t\t\tgoto exit;\n\t\t\t}\n\t\t}\n\t\t*cursor = 0;\n\t\titem_space = strchr(string, ' ');\n\t\tif (!item_space) {\n\t\t\tgoto exit;\n\t\t}\n\t\t*item_space = 0;\n\t\titem_time = atol(string);\n\t\titem_string = item_space + 1;\n\t\tif (exec_path_len == cursor - item_string && 0 == memcmp(item_string, exec_path, exec_path_len)) {\n\t\t\tif (will_write) {\n\t\t\t\tif (item_time >= 1000000000 && time_now >= 1000000000) {\n\t\t\t\t\thas_written = 1;\n#ifdef _WIN32\n\t\t\t\t\t_ltoa(time_now, string, 10);\n#else\n\t\t\t\t\tret = sprintf(string, \"%ld\", time_now);\n#endif // _WIN32\n\t\t\t\t\tstring[10] = ' ';\n\t\t\t\t\t*cursor = '\\n';\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} else if (time_now - item_time < 24 * 3600) {\n\t\t\t\treturn 0;\n\t\t\t}\n\t\t}\n\t\t*item_space = ' ';\n\t\t*cursor = '\\n';\n\t\tstring = cursor + 1;\n\t}\nwrite:\n\tif (will_write) {\n#ifdef _WIN32\n\t\tf = _wfopen(filepath, L\"wb\");\n#else\n\t\tf = fopen(filepath, \"wb\");\n#endif // _WIN32\n\t\tif (NULL == f) {\n\t\t\tgoto exit;\n\t\t}\n\t\tif (string0) {\n\t\t\tret = fwrite(string0, fsize, 1, f);\n\t\t\tif (1 != ret) {\n\t\t\t\tgoto exit;\n\t\t\t}\n\t\t}\n\t\tif (!has_written) {\n\t\t\tchar writting[20];\n#ifdef _WIN32\n\t\t\t_ltoa(time_now, writting, 10);\n\t\t\tret = fwrite(writting, strlen(writting), 1, f);\n\t\t\tif (1 != ret) {\n\t\t\t\tgoto exit;\n\t\t\t}\n\t\t\tret = fwrite(\" \", 1, 1, f);\n\t\t\tif (1 != ret) {\n\t\t\t\tgoto exit;\n\t\t\t}\n#else\n\t\t\tret = sprintf(writting, \"%ld \", time_now);\n\t\t\tret = fwrite(writting, strlen(writting), 1, f);\n\t\t\tif (1 != ret) {\n\t\t\t\tgoto exit;\n\t\t\t}\n#endif // _WIN32\n\t\t\tret = fwrite(exec_path, exec_path_len, 1, f);\n\t\t\tif (1 != ret) {\n\t\t\t\tgoto exit;\n\t\t\t}\n\t\t\tret = fwrite(\"\\n\", 1, 1, f);\n\t\t\tif (1 != ret) {\n\t\t\t\tgoto exit;\n\t\t\t}\n\t\t}\n\t}\n\nexit:\n\tif (f) {\n\t\tfclose(f);\n\t}\n\tif (string0) {\n\t\tfree(string0);\n\t}\n#ifdef _WIN32\n\tif (ppszPath) {\n\t\tCoTaskMemFree(ppszPath);\n\t}\n#else\n\tif (filepath) {\n\t\tfree(filepath);\n\t}\n#endif\n\treturn 1;\n}\n"
  },
  {
    "path": "tests/main.c",
    "content": "/*\n * Copyright (c) 2020 Minqi Pan et al.\n *\n * This file is part of libautoupdate, distributed under the MIT License\n * For full terms see the included LICENSE file\n */\n\n#include \"autoupdate.h\"\n#include \"autoupdate_internal.h\"\n\n#include <limits.h>\n#include <assert.h>\n#include <sys/stat.h>\n#include <stdint.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef _WIN32\n#include <Windows.h>\n#include <wchar.h>\n#endif\n\n#ifdef __linux__\n#include <linux/limits.h>\n#endif\n\n#define EXPECT(condition) expect(condition, __FILE__, __LINE__)\n\nstatic void expect(short condition, const char *file, int line)\n{\n\tif (condition) {\n\t\tfprintf(stderr, \".\");\n\t}\n\telse {\n\t\tfprintf(stderr, \"x\");\n\t\tfprintf(stderr, \"\\nFAILED: %s line %d\\n\", file, line);\n\t\texit(1);\n\t}\n\tfflush(stderr);\n}\n\n#ifdef _WIN32\nint main(int argc, wchar_t *wargv[])\n#else\nint main(int argc, char *argv[])\n#endif\n{\n\tint ret;\n\tstruct stat statbuf;\n\tsize_t exec_path_len;\n\tchar* exec_path;\n\n\t// test autoupdate_exepath\n#ifdef _WIN32\n\texec_path_len = 2 * MAX_PATH;\n#else\n\texec_path_len = 2 * PATH_MAX;\n#endif\n\texec_path = malloc(exec_path_len);\n\tret = autoupdate_exepath(exec_path, &exec_path_len);\n\tEXPECT(0 == ret);\n\t\n\tret = stat(exec_path, &statbuf);\n\tEXPECT(0 == ret);\n\tEXPECT(S_IFREG == (S_IFMT & statbuf.st_mode));\n\n\t// test autoupdate_should_proceed()\n\tautoupdate_should_proceed();\n\n\t// test autoupdate_should_proceed_24_hours()\n#ifdef _WIN32\n\tautoupdate_should_proceed_24_hours(argc, wargv, 0);\n\tautoupdate_should_proceed_24_hours(argc, wargv, 1);\n\tautoupdate_should_proceed_24_hours(argc, wargv, 0);\n\tautoupdate_should_proceed_24_hours(argc, wargv, 1);\n#else\n\tautoupdate_should_proceed_24_hours(argc, argv, 0);\n\tautoupdate_should_proceed_24_hours(argc, argv, 1);\n\tautoupdate_should_proceed_24_hours(argc, argv, 0);\n\tautoupdate_should_proceed_24_hours(argc, argv, 1);\n#endif\n\n        // test autoupdate()\n#ifdef _WIN32\n        autoupdate(\n                argc,\n                wargv,\n                \"enclose.io\",\n                \"80\",\n                \"/rubyc/rubyc-x64.zip\",\n                \"---^_^---\",\n\t\t1\n        );\n#endif\n\n#ifdef __linux__\n        autoupdate(\n                argc,\n                argv,\n                \"enclose.io\",\n                80,\n                \"/rubyc/rubyc-linux-x64.gz\",\n                \"---^_^---\",\n\t\t1\n        );\n#endif\n\n#ifdef __APPLE__\n        autoupdate(\n                argc,\n                argv,\n                \"enclose.io\",\n                80,\n                \"/rubyc/rubyc-darwin-x64.gz\",\n                \"---^_^---\",\n\t\t1\n        );\n#endif\n        // should never reach this point\n\treturn 1;\n}\n"
  }
]