├── tests ├── CMakeLists.txt └── cram │ ├── CMakeLists.txt │ ├── test-san_uclient-fetch.t │ ├── test_uclient-fetch.t │ └── server │ └── lorem ├── .gitignore ├── progress.h ├── uclient-test.uc ├── .gitlab └── wolfssl.yml ├── .gitlab-ci.yml ├── uclient-utils.h ├── uclient-backend.h ├── CMakeLists.txt ├── uclient-utils.c ├── uclient.h ├── progress.c ├── uclient.c ├── ucode.c ├── uclient-fetch.c └── uclient-http.c /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ADD_SUBDIRECTORY(cram) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | CMakeCache.txt 3 | CMakeFiles 4 | *.cmake 5 | *.a 6 | *.so 7 | *.dylib 8 | install_manifest.txt 9 | uclient-fetch 10 | -------------------------------------------------------------------------------- /progress.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROGRESS_H 2 | #define __PROGRESS_H 3 | 4 | #include 5 | 6 | struct progress { 7 | unsigned int last_size; 8 | unsigned int last_update_sec; 9 | unsigned int last_change_sec; 10 | unsigned int start_sec; 11 | char *curfile; 12 | }; 13 | 14 | 15 | void progress_init(struct progress *p, const char *curfile); 16 | void progress_update(struct progress *p, off_t beg_size, 17 | off_t transferred, off_t totalsize); 18 | 19 | static inline void 20 | progress_free(struct progress *p) 21 | { 22 | free(p->curfile); 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /uclient-test.uc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ucode 2 | 'use strict'; 3 | import { basename, stdout } from "fs"; 4 | let uloop = require("uloop"); 5 | let uclient = require("uclient"); 6 | 7 | function fetch_data() { 8 | let data; 9 | while (length(data = uc.read()) > 0) 10 | print(data); 11 | } 12 | 13 | let url = shift(ARGV); 14 | if (!url) { 15 | warn(`Usage: ${basename(sourcepath())} \n`); 16 | exit(1); 17 | } 18 | 19 | uloop.init(); 20 | uc = uclient.new(url, null, { 21 | header_done: (cb) => { 22 | warn(sprintf("Headers: %.J\nStatus: %.J\n", uc.get_headers(), uc.status())); 23 | }, 24 | data_read: fetch_data, 25 | data_eof: (cb) => { 26 | stdout.flush(); 27 | uloop.end(); 28 | }, 29 | error: (cb, code) => { 30 | warn(`Error: ${code}\n`); 31 | uloop.end(); 32 | } 33 | }); 34 | 35 | if (!uc.ssl_init({ verify: false })) { 36 | warn(`Failed to initialize SSL\n`); 37 | exit(1); 38 | } 39 | 40 | if (!uc.connect()) { 41 | warn(`Failed to connect\n`); 42 | exit(1); 43 | } 44 | 45 | if (!uc.request("GET")) { 46 | warn(`Failed to send request\n`); 47 | exit(1); 48 | } 49 | 50 | uloop.run(); 51 | -------------------------------------------------------------------------------- /.gitlab/wolfssl.yml: -------------------------------------------------------------------------------- 1 | .ustream-ssl wolfSSL: 2 | extends: .openwrt-native-build 3 | variables: 4 | CI_CMAKE_EXTRA_BUILD_ARGS: -DWOLFSSL=on 5 | 6 | before_script: 7 | - git clone -b $CI_WOLFSSL_TEST_BRANCH --depth 1 https://github.com/wolfSSL/wolfssl 8 | - | 9 | cd wolfssl && 10 | git log -1 && 11 | ./autogen.sh && 12 | ./configure \ 13 | --enable-sni \ 14 | --enable-opensslall \ 15 | --enable-opensslextra \ 16 | --enable-altcertchains \ 17 | --prefix=/usr && 18 | make -j$(($(nproc)+1)) all && 19 | sudo make install && cd .. 20 | 21 | - git clone https://git.openwrt.org/project/ustream-ssl.git 22 | - | 23 | cd ustream-ssl && 24 | git log -1 && 25 | export VERBOSE=1 && 26 | mkdir -p build && cd build && 27 | cmake .. -DCMAKE_INSTALL_PREFIX=/usr $CI_CMAKE_EXTRA_BUILD_ARGS && cd .. && 28 | make -j$(($(nproc)+1)) -C build && 29 | sudo make install -C build 30 | 31 | - cd $CI_PROJECT_DIR 32 | - rm -fr wolfssl ustream-ssl 33 | 34 | .ustream-ssl wolfSSL master: 35 | extends: .ustream-ssl wolfSSL 36 | variables: 37 | CI_WOLFSSL_TEST_BRANCH: master 38 | 39 | .ustream-ssl wolfSSL release: 40 | extends: .ustream-ssl wolfSSL 41 | variables: 42 | CI_WOLFSSL_TEST_BRANCH: release 43 | -------------------------------------------------------------------------------- /tests/cram/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | FIND_PACKAGE(PythonInterp 3 REQUIRED) 2 | FILE(GLOB test_cases "test_*.t") 3 | 4 | IF(CMAKE_C_COMPILER_ID STREQUAL "Clang") 5 | FILE(GLOB test_cases_san "test-san_*.t") 6 | ENDIF() 7 | 8 | SET(PYTHON_VENV_DIR "${CMAKE_CURRENT_BINARY_DIR}/.venv") 9 | SET(PYTHON_VENV_PIP "${PYTHON_VENV_DIR}/bin/pip") 10 | SET(PYTHON_VENV_CRAM "${PYTHON_VENV_DIR}/bin/cram") 11 | 12 | ADD_CUSTOM_COMMAND( 13 | OUTPUT ${PYTHON_VENV_CRAM} 14 | COMMAND ${PYTHON_EXECUTABLE} -m venv ${PYTHON_VENV_DIR} 15 | COMMAND ${PYTHON_VENV_PIP} install cram 16 | ) 17 | ADD_CUSTOM_TARGET(prepare-cram-venv ALL DEPENDS ${PYTHON_VENV_CRAM}) 18 | 19 | ADD_CUSTOM_TARGET( 20 | http-server-kill ALL 21 | COMMAND pkill --full -9 "${PYTHON_VENV_DIR}/bin/python3 -m http.server 1922 --bind 127.0.0.1" > /dev/null 2>&1 || true 22 | DEPENDS ${PYTHON_VENV_CRAM} 23 | ) 24 | 25 | ADD_CUSTOM_TARGET( 26 | http-server ALL 27 | COMMAND ${PYTHON_VENV_DIR}/bin/python3 -m http.server 1922 --bind 127.0.0.1 > /dev/null 2>&1 & ; 28 | curl --silent --retry 3 --retry-delay 1 --retry-connrefused http://127.0.0.1:1922 > /dev/null 29 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/server 30 | DEPENDS http-server-kill 31 | ) 32 | 33 | ADD_TEST( 34 | NAME cram 35 | COMMAND ${PYTHON_VENV_CRAM} ${test_cases} ${test_cases_san} 36 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 37 | ) 38 | 39 | SET_PROPERTY(TEST cram APPEND PROPERTY ENVIRONMENT "BUILD_BIN_DIR=$") 40 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | CI_ENABLE_UNIT_TESTING: 1 3 | CI_TARGET_BUILD_DEPENDS: uclient 4 | 5 | include: 6 | - remote: https://gitlab.com/ynezz/openwrt-ci/raw/master/openwrt-ci/gitlab/main.yml 7 | - local: .gitlab/wolfssl.yml 8 | 9 | .native ustream-ssl backend: 10 | extends: .openwrt-native-build 11 | 12 | before_script: 13 | - git clone https://git.openwrt.org/project/ustream-ssl.git 14 | - | 15 | cd ustream-ssl && 16 | git log -1 && 17 | export VERBOSE=1 && 18 | mkdir -p build && cd build && 19 | cmake .. -DCMAKE_INSTALL_PREFIX=/usr $CI_CMAKE_EXTRA_BUILD_ARGS && cd .. && 20 | make -j$(($(nproc)+1)) -C build && 21 | sudo make install -C build && cd .. 22 | 23 | - cd $CI_PROJECT_DIR 24 | - rm -fr ustream-ssl 25 | 26 | various native checks with ustream-ssl/wolfSSL backend (master branch): 27 | extends: .ustream-ssl wolfSSL master 28 | 29 | various native checks with ustream-ssl/wolfSSL backend (release branch): 30 | extends: .ustream-ssl wolfSSL release 31 | 32 | various native checks with ustream-ssl/OpenSSL backend: 33 | extends: .native ustream-ssl backend 34 | 35 | various native checks with ustream-ssl/mbedTLS backend: 36 | extends: .native ustream-ssl backend 37 | variables: 38 | CI_CMAKE_EXTRA_BUILD_ARGS: -DMBEDTLS=on 39 | 40 | build with Atheros ATH79 SDK (out of tree): 41 | extends: .openwrt-sdk-oot-build_ath79-generic 42 | 43 | build with Freescale i.MX 6 SDK (out of tree): 44 | extends: .openwrt-sdk-oot-build_imx6-generic 45 | 46 | build with MIPS Malta CoreLV BE SDK (out of tree): 47 | extends: .openwrt-sdk-oot-build_malta-be 48 | 49 | build with Marvell Armada Cortex A-53 SDK (out of tree): 50 | extends: .openwrt-sdk-oot-build_mvebu-cortexa53 51 | -------------------------------------------------------------------------------- /uclient-utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * uclient - ustream based protocol client library 3 | * 4 | * Copyright (C) 2014 Felix Fietkau 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #ifndef __UCLIENT_UTILS_H 19 | #define __UCLIENT_UTILS_H 20 | 21 | #include 22 | 23 | struct http_digest_data { 24 | const char *uri; 25 | const char *method; 26 | 27 | const char *auth_hash; /* H(A1) */ 28 | const char *qop; 29 | const char *nc; 30 | const char *nonce; 31 | const char *cnonce; 32 | }; 33 | 34 | static inline int base64_len(int len) 35 | { 36 | return ((len + 2) / 3) * 4; 37 | } 38 | 39 | void base64_encode(const void *inbuf, unsigned int len, void *out); 40 | void bin_to_hex(char *dest, const void *buf, int len); 41 | 42 | int uclient_urldecode(const char *in, char *out, bool decode_plus); 43 | 44 | void http_digest_calculate_auth_hash(char *dest, const char *user, const char *realm, const char *password); 45 | void http_digest_calculate_response(char *dest, const struct http_digest_data *data); 46 | 47 | char *uclient_get_url_filename(const char *url, const char *default_name); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /uclient-backend.h: -------------------------------------------------------------------------------- 1 | /* 2 | * uclient - ustream based protocol client library 3 | * 4 | * Copyright (C) 2014 Felix Fietkau 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #ifndef __UCLIENT_INTERNAL_H 19 | #define __UCLIENT_INTERNAL_H 20 | 21 | struct uclient_url; 22 | 23 | struct uclient_backend { 24 | const char * const * prefix; 25 | 26 | struct uclient *(*alloc)(void); 27 | void (*free)(struct uclient *cl); 28 | void (*update_proxy_url)(struct uclient *cl); 29 | void (*update_url)(struct uclient *cl); 30 | 31 | int (*connect)(struct uclient *cl); 32 | int (*request)(struct uclient *cl); 33 | void (*disconnect)(struct uclient *cl); 34 | 35 | int (*read)(struct uclient *cl, char *buf, unsigned int len); 36 | int (*write)(struct uclient *cl, const char *buf, unsigned int len); 37 | int (*pending_bytes)(struct uclient *cl, bool write); 38 | }; 39 | 40 | void uclient_backend_set_error(struct uclient *cl, int code); 41 | void uclient_backend_set_eof(struct uclient *cl); 42 | void uclient_backend_reset_state(struct uclient *cl); 43 | struct uclient_url *uclient_get_url(const char *url_str, const char *auth_str); 44 | struct uclient_url *uclient_get_url_location(struct uclient_url *url, const char *location); 45 | static inline void uclient_backend_read_notify(struct uclient *cl) 46 | { 47 | uloop_timeout_set(&cl->read_notify, 1); 48 | } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | INCLUDE(CheckIncludeFiles) 4 | 5 | PROJECT(uclient C) 6 | ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3) 7 | IF(CMAKE_C_COMPILER_VERSION VERSION_GREATER 6) 8 | ADD_DEFINITIONS(-Wextra -Werror=implicit-function-declaration) 9 | ADD_DEFINITIONS(-Wformat -Werror=format-security -Werror=format-nonliteral) 10 | ENDIF() 11 | ADD_DEFINITIONS(-Wno-unused-parameter -Wmissing-declarations) 12 | 13 | SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") 14 | 15 | FIND_PATH(ubox_include_dir libubox/ustream-ssl.h) 16 | INCLUDE_DIRECTORIES(${ubox_include_dir}) 17 | OPTION(BUILD_UCODE "build ucode plugin" ON) 18 | 19 | IF(BUILD_STATIC) 20 | FIND_LIBRARY(ubox_library NAMES ubox.a) 21 | ELSE(BUILD_STATIC) 22 | FIND_LIBRARY(ubox_library NAMES ubox) 23 | ENDIF(BUILD_STATIC) 24 | 25 | IF(APPLE) 26 | SET(UCODE_MODULE_LINK_OPTIONS "LINKER:-undefined,dynamic_lookup") 27 | ENDIF() 28 | 29 | SET(LIB_SOURCES uclient.c uclient-http.c uclient-utils.c) 30 | ADD_LIBRARY(uclient SHARED ${LIB_SOURCES}) 31 | TARGET_LINK_LIBRARIES(uclient ${ubox_library} dl) 32 | 33 | SET(CLI_SOURCES uclient-fetch.c progress.c) 34 | ADD_EXECUTABLE(uclient-fetch ${CLI_SOURCES}) 35 | TARGET_LINK_LIBRARIES(uclient-fetch uclient) 36 | 37 | IF(UNIT_TESTING) 38 | ADD_DEFINITIONS(-DUNIT_TESTING) 39 | ENABLE_TESTING() 40 | ADD_SUBDIRECTORY(tests) 41 | 42 | IF(CMAKE_C_COMPILER_ID STREQUAL "Clang") 43 | ADD_LIBRARY(uclient-san SHARED ${LIB_SOURCES}) 44 | TARGET_COMPILE_OPTIONS(uclient-san PRIVATE -g -fno-omit-frame-pointer -fsanitize=undefined,address,leak -fno-sanitize-recover=all) 45 | TARGET_LINK_OPTIONS(uclient-san PRIVATE -fsanitize=undefined,address,leak) 46 | TARGET_LINK_LIBRARIES(uclient-san ${ubox_library} dl) 47 | 48 | ADD_EXECUTABLE(uclient-fetch-san ${CLI_SOURCES}) 49 | TARGET_COMPILE_OPTIONS(uclient-fetch-san PRIVATE -g -fno-omit-frame-pointer -fsanitize=undefined,address,leak -fno-sanitize-recover=all) 50 | TARGET_LINK_OPTIONS(uclient-fetch-san PRIVATE -fsanitize=undefined,address,leak) 51 | TARGET_LINK_LIBRARIES(uclient-fetch-san uclient-san ${ubox_library} dl) 52 | ENDIF() 53 | ENDIF() 54 | 55 | IF(BUILD_UCODE) 56 | ADD_LIBRARY(uclient_lib MODULE ucode.c) 57 | SET_TARGET_PROPERTIES(uclient_lib PROPERTIES OUTPUT_NAME uclient PREFIX "") 58 | TARGET_LINK_OPTIONS(uclient_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) 59 | TARGET_LINK_LIBRARIES(uclient_lib uclient) 60 | INSTALL(TARGETS uclient_lib 61 | LIBRARY DESTINATION lib/ucode 62 | ) 63 | ENDIF() 64 | 65 | INSTALL(FILES uclient.h uclient-utils.h 66 | DESTINATION include/libubox 67 | ) 68 | INSTALL(TARGETS uclient uclient-fetch 69 | LIBRARY DESTINATION lib 70 | RUNTIME DESTINATION bin 71 | ) 72 | 73 | -------------------------------------------------------------------------------- /tests/cram/test-san_uclient-fetch.t: -------------------------------------------------------------------------------- 1 | check uclient-fetch usage: 2 | 3 | $ [ -n "$BUILD_BIN_DIR" ] && export PATH="$BUILD_BIN_DIR:$PATH" 4 | $ alias uc='uclient-fetch-san' 5 | 6 | $ uc 7 | Usage: uclient-fetch-san [options] 8 | Options: 9 | \t-4\t\t\t\tUse IPv4 only (esc) 10 | \t-6\t\t\t\tUse IPv6 only (esc) 11 | \t-O \t\t\tRedirect output to file (use "-" for stdout) (esc) 12 | \t-P \t\t\tSet directory for output files (esc) 13 | \t--quiet | -q\t\t\tTurn off status messages (esc) 14 | \t--continue | -c\t\t\tContinue a partially-downloaded file (esc) 15 | \t--header='Header: value'\tAdd HTTP header. Multiple allowed (esc) 16 | \t--user=\t\t\tHTTP authentication username (esc) 17 | \t--password=\t\tHTTP authentication password (esc) 18 | \t--user-agent | -U \t\tSet HTTP user agent (esc) 19 | \t--post-data=STRING\t\tuse the POST method; send STRING as the data (esc) 20 | \t--post-file=FILE\t\tuse the POST method; send FILE as the data (esc) 21 | \t--spider | -s\t\t\tSpider mode - only check file existence (esc) 22 | \t--timeout=N | -T N\t\tSet connect/request timeout to N seconds (esc) 23 | \t--proxy=on | -Y on\t\tEnable interpretation of proxy env vars (default) (esc) 24 | \t--proxy=off | -Y off | (esc) 25 | \t--no-proxy \t\tDisable interpretation of proxy env vars (esc) 26 | 27 | HTTPS options: 28 | \t--ca-certificate=\t\tLoad CA certificates from file (esc) 29 | \t--no-check-certificate\t\tdon't validate the server's certificate (esc) 30 | \t--ciphers=\t\tSet the cipher list string (esc) 31 | 32 | [1] 33 | 34 | download lorem ipsum verbose: 35 | 36 | $ uc -O lorem http://127.0.0.1:1922/lorem 37 | Downloading 'http://127.0.0.1:1922/lorem' 38 | Connecting to 127.0.0.1:1922 39 | Writing to 'lorem' 40 | \r (no-eol) (esc) 41 | lorem 100% |*******************************| 4111 0:00:00 ETA 42 | Download completed (4111 bytes) 43 | 44 | $ md5sum lorem 45 | 887943f7c25bd6cec4570c405241b425 lorem 46 | 47 | download lorem ipsum quiet: 48 | 49 | $ uc -q -O lorem http://127.0.0.1:1922/lorem 50 | 51 | $ md5sum lorem 52 | 887943f7c25bd6cec4570c405241b425 lorem 53 | 54 | check that HTTP 404 errors are handled properly: 55 | 56 | $ uc http://127.0.0.1:1922/does-not-exist 57 | Downloading 'http://127.0.0.1:1922/does-not-exist' 58 | Connecting to 127.0.0.1:1922 59 | HTTP error 404 60 | [8] 61 | 62 | $ uc -q http://127.0.0.1:1922/does-not-exist 63 | [8] 64 | 65 | check that SSL works: 66 | 67 | $ uc -q -O /dev/null 'https://www.openwrt.org' 68 | 69 | $ uc -q -O /dev/null 'https://letsencrypt.org' 70 | 71 | $ uc -O /dev/null 'https://downloads.openwrt.org/does-not-exist' 2>&1 | grep error 72 | HTTP error 404 73 | 74 | check handling of certificate issues: 75 | 76 | $ uc -O /dev/null 'https://self-signed.badssl.com/' 2>&1 | grep error 77 | Connection error: Invalid SSL certificate 78 | 79 | $ uc -O /dev/null 'https://untrusted-root.badssl.com/' 2>&1 | grep error 80 | Connection error: Invalid SSL certificate 81 | 82 | $ uc -O /dev/null 'https://expired.badssl.com/' 2>&1 | grep error 83 | Connection error: Invalid SSL certificate 84 | 85 | $ uc --ca-certificate=/dev/null -O /dev/null 'https://www.openwrt.org/' 2>&1 | grep error 86 | Connection error: Invalid SSL certificate 87 | 88 | check that certificate issues can be disabled: 89 | 90 | $ uc --no-check-certificate -q -O /dev/null 'https://self-signed.badssl.com/' 91 | 92 | $ uc --no-check-certificate -q -O /dev/null 'https://untrusted-root.badssl.com/' 93 | 94 | $ uc --no-check-certificate -q -O /dev/null 'https://expired.badssl.com/' 95 | -------------------------------------------------------------------------------- /tests/cram/test_uclient-fetch.t: -------------------------------------------------------------------------------- 1 | check uclient-fetch usage: 2 | 3 | $ [ -n "$BUILD_BIN_DIR" ] && export PATH="$BUILD_BIN_DIR:$PATH" 4 | $ alias uc='valgrind --quiet --leak-check=full uclient-fetch' 5 | 6 | $ uc 7 | Usage: uclient-fetch [options] 8 | Options: 9 | \t-4\t\t\t\tUse IPv4 only (esc) 10 | \t-6\t\t\t\tUse IPv6 only (esc) 11 | \t-O \t\t\tRedirect output to file (use "-" for stdout) (esc) 12 | \t-P \t\t\tSet directory for output files (esc) 13 | \t--quiet | -q\t\t\tTurn off status messages (esc) 14 | \t--continue | -c\t\t\tContinue a partially-downloaded file (esc) 15 | \t--header='Header: value'\tAdd HTTP header. Multiple allowed (esc) 16 | \t--user=\t\t\tHTTP authentication username (esc) 17 | \t--password=\t\tHTTP authentication password (esc) 18 | \t--user-agent | -U \t\tSet HTTP user agent (esc) 19 | \t--post-data=STRING\t\tuse the POST method; send STRING as the data (esc) 20 | \t--post-file=FILE\t\tuse the POST method; send FILE as the data (esc) 21 | \t--spider | -s\t\t\tSpider mode - only check file existence (esc) 22 | \t--timeout=N | -T N\t\tSet connect/request timeout to N seconds (esc) 23 | \t--proxy=on | -Y on\t\tEnable interpretation of proxy env vars (default) (esc) 24 | \t--proxy=off | -Y off | (esc) 25 | \t--no-proxy \t\tDisable interpretation of proxy env vars (esc) 26 | 27 | HTTPS options: 28 | \t--ca-certificate=\t\tLoad CA certificates from file (esc) 29 | \t--no-check-certificate\t\tdon't validate the server's certificate (esc) 30 | \t--ciphers=\t\tSet the cipher list string (esc) 31 | 32 | [1] 33 | 34 | download lorem ipsum verbose: 35 | 36 | $ uc -O lorem http://127.0.0.1:1922/lorem 37 | Downloading 'http://127.0.0.1:1922/lorem' 38 | Connecting to 127.0.0.1:1922 39 | Writing to 'lorem' 40 | \r (no-eol) (esc) 41 | lorem 100% |*******************************| 4111 0:00:00 ETA 42 | Download completed (4111 bytes) 43 | 44 | $ md5sum lorem 45 | 887943f7c25bd6cec4570c405241b425 lorem 46 | 47 | download lorem ipsum quiet: 48 | 49 | $ uc -q -O lorem http://127.0.0.1:1922/lorem 50 | 51 | $ md5sum lorem 52 | 887943f7c25bd6cec4570c405241b425 lorem 53 | 54 | check that HTTP 404 errors are handled properly: 55 | 56 | $ uc http://127.0.0.1:1922/does-not-exist 57 | Downloading 'http://127.0.0.1:1922/does-not-exist' 58 | Connecting to 127.0.0.1:1922 59 | HTTP error 404 60 | [8] 61 | 62 | $ uc -q http://127.0.0.1:1922/does-not-exist 63 | [8] 64 | 65 | check that SSL works: 66 | 67 | $ uc -q -O /dev/null 'https://www.openwrt.org' 68 | 69 | $ uc -q -O /dev/null 'https://letsencrypt.org' 70 | 71 | $ uc -O /dev/null 'https://downloads.openwrt.org/does-not-exist' 2>&1 | grep error 72 | HTTP error 404 73 | 74 | check handling of certificate issues: 75 | 76 | $ uc -O /dev/null 'https://self-signed.badssl.com/' 2>&1 | grep error 77 | Connection error: Invalid SSL certificate 78 | 79 | $ uc -O /dev/null 'https://untrusted-root.badssl.com/' 2>&1 | grep error 80 | Connection error: Invalid SSL certificate 81 | 82 | $ uc -O /dev/null 'https://expired.badssl.com/' 2>&1 | grep error 83 | Connection error: Invalid SSL certificate 84 | 85 | $ uc --ca-certificate=/dev/null -O /dev/null 'https://www.openwrt.org/' 2>&1 | grep error 86 | Connection error: Invalid SSL certificate 87 | 88 | check that certificate issues can be disabled: 89 | 90 | $ uc --no-check-certificate -q -O /dev/null 'https://self-signed.badssl.com/' 91 | 92 | $ uc --no-check-certificate -q -O /dev/null 'https://untrusted-root.badssl.com/' 93 | 94 | $ uc --no-check-certificate -q -O /dev/null 'https://expired.badssl.com/' 95 | -------------------------------------------------------------------------------- /tests/cram/server/lorem: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas accumsan dui 2 | diam, sit amet vehicula nisl tincidunt non. Duis orci urna, luctus porttitor 3 | viverra non, interdum id erat. Vivamus in tortor eu augue dignissim imperdiet 4 | vitae ut ligula. Nunc luctus arcu viverra dolor commodo, et pellentesque 5 | lectus convallis. Donec molestie gravida venenatis. Curabitur vitae nulla at 6 | nisi ullamcorper sagittis vitae eget arcu. Sed elementum neque metus, in 7 | sollicitudin lorem vestibulum sed. Etiam non leo id eros ultrices hendrerit. 8 | 9 | Etiam sed luctus lacus. Fusce congue quam varius, cursus enim id, varius 10 | tellus. Suspendisse at mauris blandit, tempor urna non, pharetra tortor. In 11 | laoreet turpis a sollicitudin auctor. Duis semper diam mi, at mollis dolor 12 | tristique a. Sed sed mauris diam. Curabitur vel eleifend lorem. Quisque vel 13 | erat ac nulla vestibulum elementum. Curabitur euismod mauris lorem, at 14 | vestibulum justo accumsan eget. Proin pharetra scelerisque est, eget lobortis 15 | nulla. Ut sit amet tellus pellentesque, fermentum erat vitae, euismod risus. 16 | Vivamus sed nibh ut neque efficitur lacinia hendrerit accumsan leo. 17 | Suspendisse feugiat molestie suscipit. 18 | 19 | Sed mattis elit non nibh interdum, eu volutpat sem euismod. Nullam eu nibh id 20 | ligula semper molestie quis at nisi. Nulla vulputate, risus vitae vulputate 21 | dapibus, ligula erat volutpat dui, in condimentum tortor sapien et eros. Cras 22 | sed est consectetur, iaculis est a, ultricies felis. Phasellus dignissim neque 23 | quis urna aliquam, at consequat turpis rutrum. Ut gravida dolor nisi, et 24 | aliquam massa fermentum sit amet. In eu odio vel libero suscipit dignissim et 25 | ac justo. Morbi quis sollicitudin elit. Etiam varius vel odio ac dapibus. Sed 26 | ut lorem auctor, scelerisque lacus sit amet, ultrices lorem. Mauris dui 27 | mauris, sagittis id fermentum vitae, tincidunt vel sem. Morbi viverra erat 28 | nulla, et pharetra mauris condimentum maximus. Nam orci ex, semper nec rutrum 29 | sed, gravida lobortis nisl. 30 | 31 | Vivamus eleifend ligula leo, a sodales augue blandit eget. Sed varius molestie 32 | neque vel placerat. Nam volutpat sodales metus, et blandit ipsum vulputate et. 33 | Quisque eget quam sit amet nisl blandit varius in ac neque. Pellentesque 34 | habitant morbi tristique senectus et netus et malesuada fames ac turpis 35 | egestas. Donec ultricies mattis porta. Pellentesque in sollicitudin lacus. 36 | 37 | Duis quis dui consequat lacus ullamcorper pretium at eget justo. Mauris 38 | placerat dui vel augue rutrum luctus. Fusce mi purus, faucibus nec euismod sit 39 | amet, condimentum sed sem. Ut dictum arcu et eros volutpat, et gravida erat 40 | ultricies. Nam auctor, augue vitae cursus tempor, nisi lacus hendrerit massa, 41 | a varius mauris est sit amet sapien. Cras ac varius turpis. Ut vitae ligula 42 | neque. Cras nec felis ut orci accumsan maximus. Maecenas sed tempus magna. 43 | 44 | Sed metus risus, eleifend vitae viverra nec, volutpat vitae velit. Vivamus 45 | sodales porttitor urna, quis hendrerit est tempus nec. Mauris ligula ex, 46 | commodo vitae odio vel, volutpat lacinia libero. Nulla facilisis, ex sit amet 47 | rhoncus cursus, purus turpis condimentum mi, ac euismod tellus elit eu felis. 48 | Sed ultrices dolor pulvinar, laoreet lacus in, viverra arcu. Pellentesque a 49 | turpis felis. Morbi ornare nunc a hendrerit elementum. 50 | 51 | Sed laoreet dictum consequat. Maecenas et scelerisque justo, et vulputate 52 | diam. Vivamus nec erat quis nunc rutrum consectetur. Sed venenatis orci 53 | ligula, nec luctus massa tristique non. Etiam porta, nunc scelerisque bibendum 54 | tincidunt, mauris libero consequat odio, eu tempus neque augue eget massa. 55 | Nulla eu risus fermentum, ornare ipsum a, blandit nulla. Mauris in tempus 56 | quam, eu rutrum risus. Nunc maximus fringilla ante, et suscipit mi dictum 57 | laoreet. In sed ligula dictum, maximus neque ut, tincidunt mauris. Sed 58 | ultricies mauris in neque semper, eu congue nisl ullamcorper. Aliquam ac 59 | aliquam arcu. 60 | 61 | Nulla ante sapien, egestas at mattis vel, vehicula vitae urna. In tempus at 62 | nibh consequat scelerisque. Etiam interdum placerat erat ut finibus. Sed 63 | fermentum dignissim ligula, quis vehicula nulla faucibus aliquam. Cras elit 64 | lacus, porta ut vehicula eget orci aliquam. 65 | -------------------------------------------------------------------------------- /uclient-utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * uclient - ustream based protocol client library 3 | * 4 | * Copyright (C) 2014 Felix Fietkau 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include "uclient-utils.h" 27 | 28 | static const char *b64 = 29 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 30 | 31 | void base64_encode(const void *inbuf, unsigned int len, void *outbuf) 32 | { 33 | unsigned char *out = outbuf; 34 | const uint8_t *in = inbuf; 35 | unsigned int i; 36 | int pad = len % 3; 37 | 38 | for (i = 0; i < len - pad; i += 3) { 39 | uint32_t in3 = (in[0] << 16) | (in[1] << 8) | in[2]; 40 | int k; 41 | 42 | for (k = 3; k >= 0; k--) { 43 | out[k] = b64[in3 & 0x3f]; 44 | in3 >>= 6; 45 | } 46 | in += 3; 47 | out += 4; 48 | } 49 | 50 | if (pad) { 51 | uint32_t in2 = in[0] << (16 - 6); 52 | 53 | out[3] = '='; 54 | 55 | if (pad > 1) { 56 | in2 |= in[1] << (8 - 6); 57 | out[2] = b64[in2 & 0x3f]; 58 | } else { 59 | out[2] = '='; 60 | } 61 | 62 | in2 >>= 6; 63 | out[1] = b64[in2 & 0x3f]; 64 | in2 >>= 6; 65 | out[0] = b64[in2 & 0x3f]; 66 | 67 | out += 4; 68 | } 69 | 70 | *out = '\0'; 71 | } 72 | 73 | int uclient_urldecode(const char *in, char *out, bool decode_plus) 74 | { 75 | static char dec[3]; 76 | int ret = 0; 77 | char c; 78 | 79 | while ((c = *(in++))) { 80 | if (c == '%') { 81 | if (!isxdigit(in[0]) || !isxdigit(in[1])) 82 | return -1; 83 | 84 | dec[0] = in[0]; 85 | dec[1] = in[1]; 86 | c = strtol(dec, NULL, 16); 87 | in += 2; 88 | } else if (decode_plus && c == '+') { 89 | c = ' '; 90 | } 91 | 92 | *(out++) = c; 93 | ret++; 94 | } 95 | 96 | *out = 0; 97 | return ret; 98 | } 99 | 100 | static char hex_digit(char val) 101 | { 102 | val += val > 9 ? 'a' - 10 : '0'; 103 | return val; 104 | } 105 | 106 | void bin_to_hex(char *dest, const void *buf, int len) 107 | { 108 | const uint8_t *data = buf; 109 | int i; 110 | 111 | for (i = 0; i < len; i++) { 112 | *(dest++) = hex_digit(data[i] >> 4); 113 | *(dest++) = hex_digit(data[i] & 0xf); 114 | } 115 | *dest = 0; 116 | } 117 | 118 | static void http_create_hash(char *dest, const char * const * str, int n_str) 119 | { 120 | uint32_t hash[4]; 121 | md5_ctx_t md5; 122 | int i; 123 | 124 | md5_begin(&md5); 125 | for (i = 0; i < n_str; i++) { 126 | if (i) 127 | md5_hash(":", 1, &md5); 128 | md5_hash(str[i], strlen(str[i]), &md5); 129 | } 130 | md5_end(hash, &md5); 131 | bin_to_hex(dest, &hash, sizeof(hash)); 132 | } 133 | 134 | void http_digest_calculate_auth_hash(char *dest, const char *user, const char *realm, const char *password) 135 | { 136 | const char *hash_str[] = { 137 | user, 138 | realm, 139 | password 140 | }; 141 | 142 | http_create_hash(dest, hash_str, ARRAY_SIZE(hash_str)); 143 | } 144 | 145 | void http_digest_calculate_response(char *dest, const struct http_digest_data *data) 146 | { 147 | const char *h_a2_strings[] = { 148 | data->method, 149 | data->uri, 150 | }; 151 | const char *resp_strings[] = { 152 | data->auth_hash, 153 | data->nonce, 154 | data->nc, 155 | data->cnonce, 156 | data->qop, 157 | dest, /* initialized to H(A2) first */ 158 | }; 159 | 160 | http_create_hash(dest, h_a2_strings, ARRAY_SIZE(h_a2_strings)); 161 | http_create_hash(dest, resp_strings, ARRAY_SIZE(resp_strings)); 162 | } 163 | 164 | char *uclient_get_url_filename(const char *url, const char *default_name) 165 | { 166 | const char *str; 167 | int len = strcspn(url, ";&"); 168 | 169 | while (len > 0 && url[len - 1] == '/') 170 | len--; 171 | 172 | for (str = url + len - 1; str >= url; str--) { 173 | if (*str == '/') 174 | break; 175 | } 176 | 177 | str++; 178 | len -= str - url; 179 | 180 | if (len > 0) 181 | return strndup(str, len); 182 | 183 | return strdup(default_name); 184 | } 185 | -------------------------------------------------------------------------------- /uclient.h: -------------------------------------------------------------------------------- 1 | /* 2 | * uclient - ustream based protocol client library 3 | * 4 | * Copyright (C) 2014 Felix Fietkau 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #ifndef __LIBUBOX_UCLIENT_H 19 | #define __LIBUBOX_UCLIENT_H 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #define UCLIENT_DEFAULT_TIMEOUT_MS 30000 28 | 29 | struct uclient_cb; 30 | struct uclient_backend; 31 | 32 | enum uclient_error_code { 33 | UCLIENT_ERROR_UNKNOWN, 34 | UCLIENT_ERROR_CONNECT, 35 | UCLIENT_ERROR_TIMEDOUT, 36 | UCLIENT_ERROR_SSL_INVALID_CERT, 37 | UCLIENT_ERROR_SSL_CN_MISMATCH, 38 | UCLIENT_ERROR_MISSING_SSL_CONTEXT, 39 | __UCLIENT_ERROR_MAX 40 | }; 41 | 42 | enum uclient_log_type { 43 | UCLIENT_LOG_SSL_ERROR, 44 | UCLIENT_LOG_SSL_VERIFY_ERROR, 45 | __UCLIENT_LOG_MAX 46 | }; 47 | 48 | union uclient_addr { 49 | struct sockaddr sa; 50 | struct sockaddr_in sin; 51 | struct sockaddr_in6 sin6; 52 | }; 53 | 54 | struct uclient_url { 55 | const struct uclient_backend *backend; 56 | int prefix; 57 | 58 | const char *host; 59 | const char *port; 60 | const char *location; 61 | 62 | const char *auth; 63 | }; 64 | 65 | struct uclient { 66 | const struct uclient_backend *backend; 67 | const struct uclient_cb *cb; 68 | 69 | union uclient_addr local_addr, remote_addr; 70 | 71 | struct uclient_url *proxy_url; 72 | struct uclient_url *url; 73 | int timeout_msecs; 74 | void *priv; 75 | 76 | bool eof; 77 | bool data_eof; 78 | int error_code; 79 | int status_code; 80 | int seq; 81 | struct blob_attr *meta; 82 | 83 | struct uloop_timeout connection_timeout; 84 | struct uloop_timeout read_notify; 85 | struct uloop_timeout timeout; 86 | }; 87 | 88 | struct uclient_cb { 89 | void (*data_read)(struct uclient *cl); 90 | void (*data_sent)(struct uclient *cl); 91 | void (*data_eof)(struct uclient *cl); 92 | void (*header_done)(struct uclient *cl); 93 | void (*error)(struct uclient *cl, int code); 94 | void (*log_msg)(struct uclient *cl, enum uclient_log_type type, const char *msg); 95 | }; 96 | 97 | struct uclient *uclient_new(const char *url, const char *auth_str, const struct uclient_cb *cb); 98 | void uclient_free(struct uclient *cl); 99 | 100 | int uclient_set_url(struct uclient *cl, const char *url, const char *auth); 101 | int uclient_set_proxy_url(struct uclient *cl, const char *url_str, const char *auth_str); 102 | 103 | 104 | /** 105 | * Sets connection timeout. 106 | * 107 | * Provided timeout value will be used for: 108 | * 1) Receiving HTTP response 109 | * 2) Receiving data 110 | * 111 | * In case of timeout uclient will use error callback with 112 | * UCLIENT_ERROR_TIMEDOUT code. 113 | * 114 | * @param msecs timeout in milliseconds 115 | */ 116 | int uclient_set_timeout(struct uclient *cl, int msecs); 117 | 118 | int uclient_connect(struct uclient *cl); 119 | void uclient_disconnect(struct uclient *cl); 120 | 121 | int uclient_read(struct uclient *cl, char *buf, int len); 122 | int uclient_write(struct uclient *cl, const char *buf, int len); 123 | int uclient_pending_bytes(struct uclient *cl, bool write); 124 | int uclient_request(struct uclient *cl); 125 | 126 | char *uclient_get_addr(char *dest, int *port, union uclient_addr *a); 127 | struct ustream_ssl_ctx *uclient_new_ssl_context(const struct ustream_ssl_ops **ops); 128 | 129 | /* HTTP */ 130 | extern const struct uclient_backend uclient_backend_http; 131 | 132 | int uclient_http_reset_headers(struct uclient *cl); 133 | int uclient_http_set_header(struct uclient *cl, const char *name, const char *value); 134 | int uclient_http_set_request_type(struct uclient *cl, const char *type); 135 | int uclient_http_redirect(struct uclient *cl); 136 | 137 | static inline bool uclient_http_status_redirect(struct uclient *cl) 138 | { 139 | switch (cl->status_code) { 140 | case 301: 141 | case 302: 142 | case 307: 143 | return true; 144 | default: 145 | return false; 146 | } 147 | } 148 | 149 | int uclient_http_set_ssl_ctx(struct uclient *cl, const struct ustream_ssl_ops *ops, 150 | struct ustream_ssl_ctx *ctx, bool require_validation); 151 | int uclient_http_set_address_family(struct uclient *cl, int af); 152 | const char *uclient_strerror(unsigned err); 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /progress.c: -------------------------------------------------------------------------------- 1 | /* vi: set sw=4 ts=4: */ 2 | /* 3 | * Progress bar code. 4 | */ 5 | /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff, 6 | * much of which was blatantly stolen from openssh. 7 | */ 8 | /*- 9 | * Copyright (c) 1992, 1993 10 | * The Regents of the University of California. All rights reserved. 11 | * 12 | * Redistribution and use in source and binary forms, with or without 13 | * modification, are permitted provided that the following conditions 14 | * are met: 15 | * 1. Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * 3. BSD Advertising Clause omitted per the July 22, 1999 licensing change 22 | * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change 23 | * 24 | * 4. Neither the name of the University nor the names of its contributors 25 | * may be used to endorse or promote products derived from this software 26 | * without specific prior written permission. 27 | * 28 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 29 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 30 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 31 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 32 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 33 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 34 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 35 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 36 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 37 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 38 | * SUCH DAMAGE. 39 | */ 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include "progress.h" 47 | 48 | enum { 49 | /* Seconds when xfer considered "stalled" */ 50 | STALLTIME = 5 51 | }; 52 | 53 | static int 54 | wh_helper(int value, int def_val, const char *env_name) 55 | { 56 | if (value == 0) { 57 | char *s = getenv(env_name); 58 | if (s) 59 | value = atoi(s); 60 | } 61 | if (value <= 1 || value >= 30000) 62 | value = def_val; 63 | return value; 64 | } 65 | 66 | static unsigned int 67 | get_tty2_width(void) 68 | { 69 | #ifndef TIOCGWINSZ 70 | return wh_helper(0, 80, "COLUMNS"); 71 | #else 72 | struct winsize win = {0}; 73 | ioctl(2, TIOCGWINSZ, &win); 74 | return wh_helper(win.ws_col, 80, "COLUMNS"); 75 | #endif 76 | } 77 | 78 | static unsigned int 79 | monotonic_sec(void) 80 | { 81 | struct timespec tv; 82 | clock_gettime(CLOCK_MONOTONIC, &tv); 83 | return tv.tv_sec; 84 | } 85 | 86 | void 87 | progress_init(struct progress *p, const char *curfile) 88 | { 89 | p->curfile = strdup(curfile); 90 | p->start_sec = monotonic_sec(); 91 | p->last_update_sec = p->start_sec; 92 | p->last_change_sec = p->start_sec; 93 | p->last_size = 0; 94 | } 95 | 96 | /* File already had beg_size bytes. 97 | * Then we started downloading. 98 | * We downloaded "transferred" bytes so far. 99 | * Download is expected to stop when total size (beg_size + transferred) 100 | * will be "totalsize" bytes. 101 | * If totalsize == 0, then it is unknown. 102 | */ 103 | void 104 | progress_update(struct progress *p, off_t beg_size, 105 | off_t transferred, off_t totalsize) 106 | { 107 | off_t beg_and_transferred; 108 | unsigned since_last_update, elapsed; 109 | int barlength; 110 | int kiloscale; 111 | 112 | //transferred = 1234; /* use for stall detection testing */ 113 | //totalsize = 0; /* use for unknown size download testing */ 114 | 115 | elapsed = monotonic_sec(); 116 | since_last_update = elapsed - p->last_update_sec; 117 | p->last_update_sec = elapsed; 118 | 119 | if (totalsize != 0 && transferred >= totalsize - beg_size) { 120 | /* Last call. Do not skip this update */ 121 | transferred = totalsize - beg_size; /* sanitize just in case */ 122 | } 123 | else if (since_last_update == 0) { 124 | /* 125 | * Do not update on every call 126 | * (we can be called on every network read!) 127 | */ 128 | return; 129 | } 130 | 131 | kiloscale = 0; 132 | /* 133 | * Scale sizes down if they are close to overflowing. 134 | * This allows calculations like (100 * transferred / totalsize) 135 | * without risking overflow: we guarantee 10 highest bits to be 0. 136 | * Introduced error is less than 1 / 2^12 ~= 0.025% 137 | */ 138 | if (ULONG_MAX > 0xffffffff || sizeof(off_t) == 4 || sizeof(off_t) != 8) { 139 | /* 140 | * 64-bit CPU || small off_t: in either case, 141 | * >> is cheap, single-word operation. 142 | * ... || strange off_t: also use this code 143 | * (it is safe, just suboptimal wrt code size), 144 | * because 32/64 optimized one works only for 64-bit off_t. 145 | */ 146 | if (totalsize >= (1 << 22)) { 147 | totalsize >>= 10; 148 | beg_size >>= 10; 149 | transferred >>= 10; 150 | kiloscale = 1; 151 | } 152 | } else { 153 | /* 32-bit CPU and 64-bit off_t. 154 | * Use a 40-bit shift, it is easier to do on 32-bit CPU. 155 | */ 156 | /* ONE suppresses "warning: shift count >= width of type" */ 157 | #define ONE (sizeof(off_t) > 4) 158 | if (totalsize >= (off_t)(1ULL << 54*ONE)) { 159 | totalsize = (uint32_t)(totalsize >> 32*ONE) >> 8; 160 | beg_size = (uint32_t)(beg_size >> 32*ONE) >> 8; 161 | transferred = (uint32_t)(transferred >> 32*ONE) >> 8; 162 | kiloscale = 4; 163 | } 164 | } 165 | 166 | fprintf(stderr, "\r%-20.20s", p->curfile); 167 | 168 | beg_and_transferred = beg_size + transferred; 169 | 170 | if (totalsize != 0) { 171 | unsigned ratio = 100 * beg_and_transferred / totalsize; 172 | fprintf(stderr, "%4u%%", ratio); 173 | 174 | barlength = get_tty2_width() - 49; 175 | if (barlength > 0) { 176 | /* god bless gcc for variable arrays :) */ 177 | char buf[barlength + 1]; 178 | unsigned stars = (unsigned)barlength * beg_and_transferred / totalsize; 179 | memset(buf, ' ', barlength); 180 | buf[barlength] = '\0'; 181 | memset(buf, '*', stars); 182 | fprintf(stderr, " |%s|", buf); 183 | } 184 | } 185 | 186 | while (beg_and_transferred >= 100000) { 187 | beg_and_transferred >>= 10; 188 | kiloscale++; 189 | } 190 | /* see http://en.wikipedia.org/wiki/Tera */ 191 | fprintf(stderr, "%6u%c", (unsigned)beg_and_transferred, " kMGTPEZY"[kiloscale]); 192 | 193 | since_last_update = elapsed - p->last_change_sec; 194 | if ((unsigned)transferred != p->last_size) { 195 | p->last_change_sec = elapsed; 196 | p->last_size = (unsigned)transferred; 197 | if (since_last_update >= STALLTIME) { 198 | /* We "cut out" these seconds from elapsed time 199 | * by adjusting start time */ 200 | p->start_sec += since_last_update; 201 | } 202 | since_last_update = 0; /* we are un-stalled now */ 203 | } 204 | 205 | elapsed -= p->start_sec; /* now it's "elapsed since start" */ 206 | 207 | if (since_last_update >= STALLTIME) { 208 | fprintf(stderr, " - stalled -"); 209 | } else if (!totalsize || !transferred || (int)elapsed < 0) { 210 | fprintf(stderr, " --:--:-- ETA"); 211 | } else { 212 | unsigned eta, secs, hours; 213 | 214 | totalsize -= beg_size; /* now it's "total to upload" */ 215 | 216 | /* Estimated remaining time = 217 | * estimated_sec_to_dl_totalsize_bytes - elapsed_sec = 218 | * totalsize / average_bytes_sec_so_far - elapsed = 219 | * totalsize / (transferred/elapsed) - elapsed = 220 | * totalsize * elapsed / transferred - elapsed 221 | */ 222 | eta = totalsize * elapsed / transferred - elapsed; 223 | if (eta >= 1000*60*60) 224 | eta = 1000*60*60 - 1; 225 | secs = eta % 3600; 226 | hours = eta / 3600; 227 | fprintf(stderr, "%3u:%02u:%02u ETA", hours, secs / 60, secs % 60); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /uclient.c: -------------------------------------------------------------------------------- 1 | /* 2 | * uclient - ustream based protocol client library 3 | * 4 | * Copyright (C) 2014 Felix Fietkau 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include "uclient.h" 22 | #include "uclient-utils.h" 23 | #include "uclient-backend.h" 24 | 25 | #ifdef __APPLE__ 26 | #define LIB_EXT "dylib" 27 | #else 28 | #define LIB_EXT "so" 29 | #endif 30 | 31 | char *uclient_get_addr(char *dest, int *port, union uclient_addr *a) 32 | { 33 | int portval; 34 | void *ptr; 35 | 36 | switch(a->sa.sa_family) { 37 | case AF_INET: 38 | ptr = &a->sin.sin_addr; 39 | portval = a->sin.sin_port; 40 | break; 41 | case AF_INET6: 42 | ptr = &a->sin6.sin6_addr; 43 | portval = a->sin6.sin6_port; 44 | break; 45 | default: 46 | return strcpy(dest, "Unknown"); 47 | } 48 | 49 | inet_ntop(a->sa.sa_family, ptr, dest, INET6_ADDRSTRLEN); 50 | if (port) 51 | *port = ntohs(portval); 52 | 53 | return dest; 54 | } 55 | 56 | static struct uclient_url * 57 | __uclient_get_url(const struct uclient_backend *backend, 58 | const char *host, int host_len, 59 | const char *location, const char *auth_str) 60 | { 61 | struct uclient_url *url; 62 | char *host_buf, *uri_buf, *auth_buf, *next; 63 | 64 | url = calloc_a(sizeof(*url), 65 | &host_buf, host_len + 1, 66 | &uri_buf, strlen(location) + 1, 67 | &auth_buf, auth_str ? strlen(auth_str) + 1 : 0); 68 | 69 | if (!url) 70 | return NULL; 71 | 72 | url->backend = backend; 73 | url->location = strcpy(uri_buf, location); 74 | if (host) 75 | url->host = strncpy(host_buf, host, host_len); 76 | 77 | next = strchr(host_buf, '@'); 78 | if (next) { 79 | *next = 0; 80 | url->host = next + 1; 81 | 82 | if (uclient_urldecode(host_buf, host_buf, false) < 0) 83 | goto free; 84 | 85 | url->auth = host_buf; 86 | } 87 | 88 | if (!url->auth && auth_str) 89 | url->auth = strcpy(auth_buf, auth_str); 90 | 91 | /* Literal IPv6 address */ 92 | if (*url->host == '[') { 93 | url->host++; 94 | next = strrchr(url->host, ']'); 95 | if (!next) 96 | goto free; 97 | 98 | *(next++) = 0; 99 | if (*next == ':') 100 | url->port = next + 1; 101 | } else { 102 | next = strrchr(url->host, ':'); 103 | if (next) { 104 | *next = 0; 105 | url->port = next + 1; 106 | } 107 | } 108 | 109 | return url; 110 | 111 | free: 112 | free(url); 113 | return NULL; 114 | } 115 | 116 | static const char * 117 | uclient_split_host(const char *base, int *host_len) 118 | { 119 | char *next, *location; 120 | 121 | next = strchr(base, '/'); 122 | if (next) { 123 | location = next; 124 | *host_len = next - base; 125 | } else { 126 | location = "/"; 127 | *host_len = strlen(base); 128 | } 129 | 130 | return location; 131 | } 132 | 133 | struct uclient_url __hidden * 134 | uclient_get_url_location(struct uclient_url *url, const char *location) 135 | { 136 | struct uclient_url *new_url; 137 | char *host_buf, *uri_buf, *auth_buf, *port_buf; 138 | int host_len = strlen(url->host) + 1; 139 | int auth_len = url->auth ? strlen(url->auth) + 1 : 0; 140 | int port_len = url->port ? strlen(url->port) + 1 : 0; 141 | int uri_len; 142 | 143 | if (strstr(location, "://")) 144 | return uclient_get_url(location, url->auth); 145 | 146 | if (location[0] == '/') 147 | uri_len = strlen(location) + 1; 148 | else 149 | uri_len = strlen(url->location) + strlen(location) + 2; 150 | 151 | new_url = calloc_a(sizeof(*url), 152 | &host_buf, host_len, 153 | &port_buf, port_len, 154 | &uri_buf, uri_len, 155 | &auth_buf, auth_len); 156 | 157 | if (!new_url) 158 | return NULL; 159 | 160 | new_url->backend = url->backend; 161 | new_url->prefix = url->prefix; 162 | new_url->host = strcpy(host_buf, url->host); 163 | if (url->port) 164 | new_url->port = strcpy(port_buf, url->port); 165 | if (url->auth) 166 | new_url->auth = strcpy(auth_buf, url->auth); 167 | 168 | new_url->location = uri_buf; 169 | if (location[0] == '/') 170 | strcpy(uri_buf, location); 171 | else { 172 | int len = strcspn(url->location, "?#"); 173 | char *buf = uri_buf; 174 | 175 | memcpy(buf, url->location, len); 176 | if (buf[len - 1] != '/') { 177 | buf[len] = '/'; 178 | len++; 179 | } 180 | 181 | buf += len; 182 | strcpy(buf, location); 183 | } 184 | 185 | return new_url; 186 | } 187 | 188 | struct uclient_url __hidden * 189 | uclient_get_url(const char *url_str, const char *auth_str) 190 | { 191 | static const struct uclient_backend *backends[] = { 192 | &uclient_backend_http, 193 | }; 194 | 195 | const struct uclient_backend *backend; 196 | const char * const *prefix = NULL; 197 | struct uclient_url *url; 198 | const char *location; 199 | int host_len; 200 | unsigned int i; 201 | 202 | for (i = 0; i < ARRAY_SIZE(backends); i++) { 203 | int prefix_len = 0; 204 | 205 | for (prefix = backends[i]->prefix; *prefix; prefix++) { 206 | prefix_len = strlen(*prefix); 207 | 208 | if (!strncmp(url_str, *prefix, prefix_len)) 209 | break; 210 | } 211 | 212 | if (!*prefix) 213 | continue; 214 | 215 | url_str += prefix_len; 216 | backend = backends[i]; 217 | break; 218 | } 219 | 220 | if (!*prefix) 221 | return NULL; 222 | 223 | location = uclient_split_host(url_str, &host_len); 224 | url = __uclient_get_url(backend, url_str, host_len, location, auth_str); 225 | if (!url) 226 | return NULL; 227 | 228 | url->prefix = prefix - backend->prefix; 229 | return url; 230 | } 231 | 232 | static void uclient_connection_timeout(struct uloop_timeout *timeout) 233 | { 234 | struct uclient *cl = container_of(timeout, struct uclient, connection_timeout); 235 | 236 | if (cl->backend->disconnect) 237 | cl->backend->disconnect(cl); 238 | 239 | uclient_backend_set_error(cl, UCLIENT_ERROR_TIMEDOUT); 240 | } 241 | 242 | static void __uclient_read_notify(struct uloop_timeout *timeout) 243 | { 244 | struct uclient *cl = container_of(timeout, struct uclient, read_notify); 245 | 246 | if (cl->cb->data_read) 247 | cl->cb->data_read(cl); 248 | } 249 | 250 | struct uclient *uclient_new(const char *url_str, const char *auth_str, const struct uclient_cb *cb) 251 | { 252 | struct uclient *cl; 253 | struct uclient_url *url; 254 | 255 | url = uclient_get_url(url_str, auth_str); 256 | if (!url) 257 | return NULL; 258 | 259 | cl = url->backend->alloc(); 260 | if (!cl) 261 | return NULL; 262 | 263 | cl->backend = url->backend; 264 | cl->cb = cb; 265 | cl->url = url; 266 | cl->timeout_msecs = UCLIENT_DEFAULT_TIMEOUT_MS; 267 | cl->connection_timeout.cb = uclient_connection_timeout; 268 | cl->read_notify.cb = __uclient_read_notify; 269 | 270 | return cl; 271 | } 272 | 273 | int uclient_set_proxy_url(struct uclient *cl, const char *url_str, const char *auth_str) 274 | { 275 | const struct uclient_backend *backend = cl->backend; 276 | struct uclient_url *url; 277 | int host_len; 278 | char *next, *host; 279 | 280 | if (!backend->update_proxy_url) 281 | return -1; 282 | 283 | next = strstr(url_str, "://"); 284 | if (!next) 285 | return -1; 286 | 287 | host = next + 3; 288 | uclient_split_host(host, &host_len); 289 | 290 | url = __uclient_get_url(NULL, host, host_len, url_str, auth_str); 291 | if (!url) 292 | return -1; 293 | 294 | free(cl->proxy_url); 295 | cl->proxy_url = url; 296 | 297 | if (backend->update_proxy_url) 298 | backend->update_proxy_url(cl); 299 | 300 | return 0; 301 | } 302 | 303 | int uclient_set_url(struct uclient *cl, const char *url_str, const char *auth_str) 304 | { 305 | const struct uclient_backend *backend = cl->backend; 306 | struct uclient_url *url; 307 | 308 | url = uclient_get_url(url_str, auth_str); 309 | if (!url) 310 | return -1; 311 | 312 | if (url->backend != cl->backend) { 313 | free(url); 314 | return -1; 315 | } 316 | 317 | free(cl->proxy_url); 318 | cl->proxy_url = NULL; 319 | 320 | free(cl->url); 321 | cl->url = url; 322 | 323 | if (backend->update_url) 324 | backend->update_url(cl); 325 | 326 | return 0; 327 | } 328 | 329 | int uclient_set_timeout(struct uclient *cl, int msecs) 330 | { 331 | if (msecs <= 0) 332 | return -EINVAL; 333 | 334 | cl->timeout_msecs = msecs; 335 | 336 | return 0; 337 | } 338 | 339 | int uclient_connect(struct uclient *cl) 340 | { 341 | return cl->backend->connect(cl); 342 | } 343 | 344 | void uclient_free(struct uclient *cl) 345 | { 346 | struct uclient_url *url = cl->url; 347 | 348 | if (cl->backend->free) 349 | cl->backend->free(cl); 350 | else 351 | free(cl); 352 | 353 | free(url); 354 | } 355 | 356 | int uclient_write(struct uclient *cl, const char *buf, int len) 357 | { 358 | if (!cl->backend->write) 359 | return -1; 360 | 361 | return cl->backend->write(cl, buf, len); 362 | } 363 | 364 | int uclient_request(struct uclient *cl) 365 | { 366 | int err; 367 | 368 | if (!cl->backend->request) 369 | return -1; 370 | 371 | err = cl->backend->request(cl); 372 | if (err) 373 | return err; 374 | 375 | uloop_timeout_set(&cl->connection_timeout, cl->timeout_msecs); 376 | 377 | return 0; 378 | } 379 | 380 | struct ustream_ssl_ctx *uclient_new_ssl_context(const struct ustream_ssl_ops **ops) 381 | { 382 | static const struct ustream_ssl_ops *ssl_ops; 383 | void *dlh; 384 | 385 | if (!ssl_ops) { 386 | dlh = dlopen("libustream-ssl." LIB_EXT, RTLD_LAZY | RTLD_LOCAL); 387 | if (!dlh) 388 | return NULL; 389 | 390 | ssl_ops = dlsym(dlh, "ustream_ssl_ops"); 391 | if (!ssl_ops) { 392 | dlclose(dlh); 393 | return NULL; 394 | } 395 | } 396 | 397 | *ops = ssl_ops; 398 | return ssl_ops->context_new(false); 399 | } 400 | 401 | int uclient_read(struct uclient *cl, char *buf, int len) 402 | { 403 | if (!cl->backend->read) 404 | return -1; 405 | 406 | return cl->backend->read(cl, buf, len); 407 | } 408 | 409 | int uclient_pending_bytes(struct uclient *cl, bool write) 410 | { 411 | if (!cl->backend->pending_bytes) 412 | return -1; 413 | 414 | return cl->backend->pending_bytes(cl, write); 415 | } 416 | 417 | void uclient_disconnect(struct uclient *cl) 418 | { 419 | uloop_timeout_cancel(&cl->connection_timeout); 420 | uloop_timeout_cancel(&cl->timeout); 421 | uloop_timeout_cancel(&cl->read_notify); 422 | 423 | if (!cl->backend->disconnect) 424 | return; 425 | 426 | cl->backend->disconnect(cl); 427 | } 428 | 429 | static void __uclient_backend_change_state(struct uloop_timeout *timeout) 430 | { 431 | struct uclient *cl = container_of(timeout, struct uclient, timeout); 432 | 433 | if (cl->error_code && cl->cb->error) 434 | cl->cb->error(cl, cl->error_code); 435 | else if (cl->eof && cl->cb->data_eof) 436 | cl->cb->data_eof(cl); 437 | } 438 | 439 | static void uclient_backend_change_state(struct uclient *cl) 440 | { 441 | cl->timeout.cb = __uclient_backend_change_state; 442 | uloop_timeout_set(&cl->timeout, 1); 443 | } 444 | 445 | void __hidden uclient_backend_set_error(struct uclient *cl, int code) 446 | { 447 | if (cl->error_code) 448 | return; 449 | 450 | uloop_timeout_cancel(&cl->connection_timeout); 451 | cl->error_code = code; 452 | uclient_backend_change_state(cl); 453 | } 454 | 455 | void __hidden uclient_backend_set_eof(struct uclient *cl) 456 | { 457 | if (cl->eof || cl->error_code) 458 | return; 459 | 460 | uloop_timeout_cancel(&cl->connection_timeout); 461 | cl->eof = true; 462 | uclient_backend_change_state(cl); 463 | } 464 | 465 | void __hidden uclient_backend_reset_state(struct uclient *cl) 466 | { 467 | cl->data_eof = false; 468 | cl->eof = false; 469 | cl->error_code = 0; 470 | uloop_timeout_cancel(&cl->timeout); 471 | uloop_timeout_cancel(&cl->read_notify); 472 | } 473 | 474 | const char * uclient_strerror(unsigned err) 475 | { 476 | switch (err) { 477 | case UCLIENT_ERROR_UNKNOWN: 478 | return "unknown error"; 479 | case UCLIENT_ERROR_CONNECT: 480 | return "connect failed"; 481 | case UCLIENT_ERROR_TIMEDOUT: 482 | return "timeout"; 483 | case UCLIENT_ERROR_SSL_INVALID_CERT: 484 | return "ssl invalid cert"; 485 | case UCLIENT_ERROR_SSL_CN_MISMATCH: 486 | return "ssl cn mismatch"; 487 | case UCLIENT_ERROR_MISSING_SSL_CONTEXT: 488 | return "missing ssl context"; 489 | default: 490 | return "invalid error code"; 491 | } 492 | } 493 | -------------------------------------------------------------------------------- /ucode.c: -------------------------------------------------------------------------------- 1 | /* 2 | * uclient - ustream based protocol client library - ucode binding 3 | * 4 | * Copyright (C) 2024 Felix Fietkau 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include "uclient.h" 22 | 23 | static uc_resource_type_t *uc_uclient_type; 24 | static uc_value_t *registry; 25 | static uc_vm_t *uc_vm; 26 | 27 | struct uc_uclient_priv { 28 | struct uclient_cb cb; 29 | const struct ustream_ssl_ops *ssl_ops; 30 | struct ustream_ssl_ctx *ssl_ctx; 31 | uc_value_t *resource; 32 | unsigned int idx; 33 | int offset; 34 | }; 35 | 36 | static void uc_uclient_register(struct uc_uclient_priv *ucl, uc_value_t *cb) 37 | { 38 | size_t i, len; 39 | 40 | len = ucv_array_length(registry); 41 | for (i = 0; i < len; i++) 42 | if (!ucv_array_get(registry, i)) 43 | break; 44 | 45 | ucv_array_set(registry, i, ucv_get(cb)); 46 | ucl->idx = i; 47 | } 48 | 49 | static void free_uclient(void *ptr) 50 | { 51 | struct uclient *cl = ptr; 52 | struct uc_uclient_priv *ucl; 53 | 54 | if (!cl) 55 | return; 56 | 57 | ucl = cl->priv; 58 | ucv_array_set(registry, ucl->idx, NULL); 59 | ucv_array_set(registry, ucl->idx + 1, NULL); 60 | uclient_free(cl); 61 | free(ucl); 62 | } 63 | 64 | static uc_value_t * 65 | uc_uclient_free(uc_vm_t *vm, size_t nargs) 66 | { 67 | struct uclient **cl = uc_fn_this("uclient"); 68 | 69 | free_uclient(*cl); 70 | *cl = NULL; 71 | 72 | return NULL; 73 | } 74 | 75 | static uc_value_t * 76 | uc_uclient_ssl_init(uc_vm_t *vm, size_t nargs) 77 | { 78 | struct uclient *cl = uc_fn_thisval("uclient"); 79 | const struct ustream_ssl_ops *ops; 80 | struct ustream_ssl_ctx *ctx; 81 | struct uc_uclient_priv *ucl; 82 | uc_value_t *args = uc_fn_arg(0); 83 | bool verify = false; 84 | uc_value_t *cur; 85 | 86 | if (!cl) 87 | return NULL; 88 | 89 | ucl = cl->priv; 90 | if (ucl->ssl_ctx) { 91 | uclient_http_set_ssl_ctx(cl, NULL, NULL, false); 92 | ucl->ssl_ctx = NULL; 93 | ucl->ssl_ops = NULL; 94 | } 95 | 96 | ctx = uclient_new_ssl_context(&ops); 97 | if (!ctx) 98 | return NULL; 99 | 100 | ucl->ssl_ops = ops; 101 | ucl->ssl_ctx = ctx; 102 | 103 | if ((cur = ucv_object_get(args, "cert_file", NULL)) != NULL) { 104 | const char *str = ucv_string_get(cur); 105 | if (!str || ops->context_set_crt_file(ctx, str)) 106 | goto err; 107 | } 108 | 109 | if ((cur = ucv_object_get(args, "key_file", NULL)) != NULL) { 110 | const char *str = ucv_string_get(cur); 111 | if (!str || ops->context_set_key_file(ctx, str)) 112 | goto err; 113 | } 114 | 115 | if ((cur = ucv_object_get(args, "ca_files", NULL)) != NULL) { 116 | size_t len; 117 | 118 | if (ucv_type(cur) != UC_ARRAY) 119 | goto err; 120 | 121 | len = ucv_array_length(cur); 122 | for (size_t i = 0; i < len; i++) { 123 | uc_value_t *c = ucv_array_get(cur, i); 124 | const char *str; 125 | 126 | if (!c) 127 | continue; 128 | 129 | str = ucv_string_get(c); 130 | if (!str) 131 | goto err; 132 | 133 | ops->context_add_ca_crt_file(ctx, str); 134 | } 135 | 136 | verify = true; 137 | } 138 | 139 | if ((cur = ucv_object_get(args, "verify", NULL)) != NULL) 140 | verify = ucv_is_truish(cur); 141 | 142 | ops->context_set_require_validation(ctx, verify); 143 | uclient_http_set_ssl_ctx(cl, ops, ctx, verify); 144 | 145 | return ucv_boolean_new(true); 146 | 147 | err: 148 | ops->context_free(ctx); 149 | return NULL; 150 | } 151 | 152 | static uc_value_t * 153 | uc_uclient_set_timeout(uc_vm_t *vm, size_t nargs) 154 | { 155 | struct uclient *cl = uc_fn_thisval("uclient"); 156 | uc_value_t *val = uc_fn_arg(0); 157 | 158 | if (!cl || ucv_type(val) != UC_INTEGER) 159 | return NULL; 160 | 161 | if (uclient_set_timeout(cl, ucv_int64_get(val))) 162 | return NULL; 163 | 164 | return ucv_boolean_new(true); 165 | } 166 | 167 | static uc_value_t * 168 | uc_uclient_set_url(uc_vm_t *vm, size_t nargs) 169 | { 170 | struct uclient *cl = uc_fn_thisval("uclient"); 171 | uc_value_t *url = uc_fn_arg(0); 172 | uc_value_t *auth_str = uc_fn_arg(1); 173 | 174 | if (!cl || ucv_type(url) != UC_STRING || 175 | (auth_str && ucv_type(auth_str) != UC_STRING)) 176 | return NULL; 177 | 178 | if (uclient_set_url(cl, ucv_string_get(url), ucv_string_get(auth_str))) 179 | return NULL; 180 | 181 | return ucv_boolean_new(true); 182 | } 183 | 184 | static uc_value_t * 185 | uc_uclient_set_proxy_url(uc_vm_t *vm, size_t nargs) 186 | { 187 | struct uclient *cl = uc_fn_thisval("uclient"); 188 | uc_value_t *url = uc_fn_arg(0); 189 | uc_value_t *auth_str = uc_fn_arg(1); 190 | 191 | if (!cl || ucv_type(url) != UC_STRING || 192 | (auth_str && ucv_type(auth_str) != UC_STRING)) 193 | return NULL; 194 | 195 | if (uclient_set_proxy_url(cl, ucv_string_get(url), ucv_string_get(auth_str))) 196 | return NULL; 197 | 198 | return ucv_boolean_new(true); 199 | } 200 | 201 | static uc_value_t * 202 | uc_uclient_get_headers(uc_vm_t *vm, size_t nargs) 203 | { 204 | struct uclient *cl = uc_fn_thisval("uclient"); 205 | struct blob_attr *cur; 206 | uc_value_t *ret; 207 | size_t rem; 208 | 209 | if (!cl) 210 | return NULL; 211 | 212 | ret = ucv_object_new(uc_vm); 213 | blobmsg_for_each_attr(cur, cl->meta, rem) { 214 | uc_value_t *str; 215 | 216 | if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) 217 | continue; 218 | 219 | str = ucv_string_new(blobmsg_get_string(cur)); 220 | ucv_object_add(ret, blobmsg_name(cur), ucv_get(str)); 221 | } 222 | 223 | return ret; 224 | } 225 | 226 | static uc_value_t * 227 | uc_uclient_connect(uc_vm_t *vm, size_t nargs) 228 | { 229 | struct uclient *cl = uc_fn_thisval("uclient"); 230 | 231 | if (!cl || uclient_connect(cl)) 232 | return NULL; 233 | 234 | return ucv_boolean_new(true); 235 | } 236 | 237 | static uc_value_t * 238 | uc_uclient_disconnect(uc_vm_t *vm, size_t nargs) 239 | { 240 | struct uclient *cl = uc_fn_thisval("uclient"); 241 | 242 | if (!cl) 243 | return NULL; 244 | 245 | uclient_disconnect(cl); 246 | 247 | return ucv_boolean_new(true); 248 | } 249 | 250 | static uc_value_t * 251 | __uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg) 252 | { 253 | struct uc_uclient_priv *ucl = cl->priv; 254 | uc_vm_t *vm = uc_vm; 255 | uc_value_t *cb, *cb_obj; 256 | 257 | cb_obj = ucv_array_get(registry, ucl->idx); 258 | if (!cb_obj) 259 | return NULL; 260 | 261 | cb = ucv_property_get(cb_obj, name); 262 | if (!cb) 263 | return NULL; 264 | 265 | if (!ucv_is_callable(cb)) 266 | return NULL; 267 | 268 | uc_vm_stack_push(vm, ucv_get(ucl->resource)); 269 | uc_vm_stack_push(vm, ucv_get(cb)); 270 | uc_vm_stack_push(vm, ucv_get(cb_obj)); 271 | if (arg) 272 | uc_vm_stack_push(vm, ucv_get(arg)); 273 | 274 | if (uc_vm_call(vm, true, !!arg + 1) != EXCEPTION_NONE) { 275 | if (vm->exhandler) 276 | vm->exhandler(vm, &vm->exception); 277 | return NULL; 278 | } 279 | 280 | return uc_vm_stack_pop(vm); 281 | } 282 | 283 | static void 284 | uc_write_str(struct uclient *cl, uc_value_t *val) 285 | { 286 | uclient_write(cl, ucv_string_get(val), ucv_string_length(val)); 287 | } 288 | 289 | static bool uc_cb_data_write(struct uclient *cl) 290 | { 291 | struct uc_uclient_priv *ucl = cl->priv; 292 | bool ret = false; 293 | uc_value_t *val; 294 | size_t len; 295 | 296 | val = __uc_uclient_cb(cl, "get_post_data", ucv_int64_new(ucl->offset)); 297 | if (ucv_type(val) != UC_STRING) 298 | goto out; 299 | 300 | len = ucv_string_length(val); 301 | if (!len) 302 | goto out; 303 | 304 | ucl->offset += len; 305 | uc_write_str(cl, val); 306 | ret = true; 307 | 308 | out: 309 | ucv_put(val); 310 | return ret; 311 | } 312 | 313 | static uc_value_t * 314 | uc_uclient_request(uc_vm_t *vm, size_t nargs) 315 | { 316 | struct uclient *cl = uc_fn_thisval("uclient"); 317 | struct uc_uclient_priv *ucl; 318 | uc_value_t *type = uc_fn_arg(0); 319 | uc_value_t *arg = uc_fn_arg(1); 320 | uc_value_t *cur; 321 | const char *type_str = ucv_string_get(type); 322 | 323 | if (!cl || !type_str) 324 | return NULL; 325 | 326 | ucl = cl->priv; 327 | ucl->offset = 0; 328 | 329 | if (uclient_http_set_request_type(cl, type_str)) 330 | return NULL; 331 | 332 | uclient_http_reset_headers(cl); 333 | 334 | if ((cur = ucv_property_get(arg, "headers")) != NULL) { 335 | if (ucv_type(cur) != UC_OBJECT) 336 | return NULL; 337 | 338 | ucv_object_foreach(cur, key, val) { 339 | char *str; 340 | 341 | if (!val) 342 | continue; 343 | 344 | if (ucv_type(val) == UC_STRING) { 345 | uclient_http_set_header(cl, key, ucv_string_get(val)); 346 | continue; 347 | } 348 | 349 | str = ucv_to_string(uc_vm, val); 350 | uclient_http_set_header(cl, key, str); 351 | free(str); 352 | } 353 | } 354 | 355 | if ((cur = ucv_property_get(arg, "post_data")) != NULL) { 356 | if (ucv_type(cur) != UC_STRING) 357 | return NULL; 358 | 359 | uc_write_str(cl, cur); 360 | } 361 | 362 | while (uc_cb_data_write(cl)) 363 | if (uclient_pending_bytes(cl, true)) 364 | return ucv_boolean_new(true); 365 | 366 | ucl->offset = -1; 367 | if (uclient_request(cl)) 368 | return NULL; 369 | 370 | return ucv_boolean_new(true); 371 | } 372 | 373 | static uc_value_t * 374 | uc_uclient_redirect(uc_vm_t *vm, size_t nargs) 375 | { 376 | struct uclient *cl = uc_fn_thisval("uclient"); 377 | 378 | if (!cl || uclient_http_redirect(cl)) 379 | return NULL; 380 | 381 | return ucv_boolean_new(true); 382 | } 383 | 384 | static uc_value_t * 385 | uc_uclient_status(uc_vm_t *vm, size_t nargs) 386 | { 387 | struct uclient *cl = uc_fn_thisval("uclient"); 388 | char addr[INET6_ADDRSTRLEN]; 389 | uc_value_t *ret; 390 | int port; 391 | 392 | if (!cl) 393 | return NULL; 394 | 395 | ret = ucv_object_new(vm); 396 | ucv_object_add(ret, "eof", ucv_boolean_new(cl->eof)); 397 | ucv_object_add(ret, "data_eof", ucv_boolean_new(cl->data_eof)); 398 | ucv_object_add(ret, "status", ucv_int64_new(cl->status_code)); 399 | ucv_object_add(ret, "redirect", ucv_boolean_new(uclient_http_status_redirect(cl))); 400 | 401 | uclient_get_addr(addr, &port, &cl->local_addr); 402 | ucv_object_add(ret, "local_addr", ucv_get(ucv_string_new(addr))); 403 | ucv_object_add(ret, "local_port", ucv_get(ucv_int64_new(port))); 404 | 405 | uclient_get_addr(addr, &port, &cl->remote_addr); 406 | ucv_object_add(ret, "remote_addr", ucv_get(ucv_string_new(addr))); 407 | ucv_object_add(ret, "remote_port", ucv_get(ucv_int64_new(port))); 408 | 409 | return ret; 410 | } 411 | 412 | static uc_value_t * 413 | uc_uclient_read(uc_vm_t *vm, size_t nargs) 414 | { 415 | struct uclient *cl = uc_fn_thisval("uclient"); 416 | size_t len = ucv_int64_get(uc_fn_arg(0)); 417 | uc_stringbuf_t *strbuf = NULL; 418 | static char buf[4096]; 419 | int cur; 420 | 421 | if (!cl) 422 | return NULL; 423 | 424 | if (!len) 425 | len = sizeof(buf); 426 | 427 | while (len > 0) { 428 | cur = uclient_read(cl, buf, len); 429 | if (cur <= 0) 430 | break; 431 | 432 | if (!strbuf) 433 | strbuf = ucv_stringbuf_new(); 434 | 435 | ucv_stringbuf_addstr(strbuf, buf, cur); 436 | len -= cur; 437 | } 438 | 439 | if (!strbuf) 440 | return NULL; 441 | 442 | return ucv_stringbuf_finish(strbuf); 443 | } 444 | 445 | static void 446 | uc_uclient_cb(struct uclient *cl, const char *name, uc_value_t *arg) 447 | { 448 | ucv_put(__uc_uclient_cb(cl, name, arg)); 449 | } 450 | 451 | static void uc_cb_data_read(struct uclient *cl) 452 | { 453 | uc_uclient_cb(cl, "data_read", NULL); 454 | } 455 | 456 | static void uc_cb_data_sent(struct uclient *cl) 457 | { 458 | struct uc_uclient_priv *ucl = cl->priv; 459 | 460 | if (ucl->offset < 0 || uclient_pending_bytes(cl, true)) 461 | return; 462 | 463 | while (uc_cb_data_write(cl)) 464 | if (uclient_pending_bytes(cl, true)) 465 | return; 466 | 467 | ucl->offset = -1; 468 | uclient_request(cl); 469 | } 470 | 471 | static void uc_cb_data_eof(struct uclient *cl) 472 | { 473 | uc_uclient_cb(cl, "data_eof", NULL); 474 | } 475 | 476 | static void uc_cb_header_done(struct uclient *cl) 477 | { 478 | uc_uclient_cb(cl, "header_done", NULL); 479 | } 480 | 481 | static void uc_cb_error(struct uclient *cl, int code) 482 | { 483 | uc_uclient_cb(cl, "error", ucv_int64_new(code)); 484 | } 485 | 486 | static uc_value_t * 487 | uc_uclient_new(uc_vm_t *vm, size_t nargs) 488 | { 489 | struct uc_uclient_priv *ucl; 490 | uc_value_t *url = uc_fn_arg(0); 491 | uc_value_t *auth_str = uc_fn_arg(1); 492 | uc_value_t *cb = uc_fn_arg(2); 493 | static bool _init_done; 494 | struct uclient *cl; 495 | 496 | if (!_init_done) { 497 | uloop_init(); 498 | _init_done = true; 499 | } 500 | 501 | uc_vm = vm; 502 | 503 | if (ucv_type(url) != UC_STRING || 504 | (auth_str && ucv_type(auth_str) != UC_STRING) || 505 | ucv_type(cb) != UC_OBJECT) 506 | return NULL; 507 | 508 | ucl = calloc(1, sizeof(*ucl)); 509 | if (ucv_property_get(cb, "data_read")) 510 | ucl->cb.data_read = uc_cb_data_read; 511 | if (ucv_property_get(cb, "get_post_data")) 512 | ucl->cb.data_sent = uc_cb_data_sent; 513 | if (ucv_property_get(cb, "data_eof")) 514 | ucl->cb.data_eof = uc_cb_data_eof; 515 | if (ucv_property_get(cb, "header_done")) 516 | ucl->cb.header_done = uc_cb_header_done; 517 | if (ucv_property_get(cb, "error")) 518 | ucl->cb.error = uc_cb_error; 519 | 520 | cl = uclient_new(ucv_string_get(url), ucv_string_get(auth_str), &ucl->cb); 521 | if (!cl) { 522 | free(ucl); 523 | return NULL; 524 | } 525 | 526 | cl->priv = ucl; 527 | uc_uclient_register(ucl, cb); 528 | ucl->resource = ucv_resource_new(uc_uclient_type, cl); 529 | 530 | return ucl->resource; 531 | } 532 | static const uc_function_list_t uclient_fns[] = { 533 | { "free", uc_uclient_free }, 534 | { "ssl_init", uc_uclient_ssl_init }, 535 | { "set_url", uc_uclient_set_url }, 536 | { "set_proxy_url", uc_uclient_set_proxy_url }, 537 | { "set_timeout", uc_uclient_set_timeout }, 538 | { "get_headers", uc_uclient_get_headers }, 539 | 540 | { "connect", uc_uclient_connect }, 541 | { "disconnect", uc_uclient_disconnect }, 542 | { "request", uc_uclient_request }, 543 | { "redirect", uc_uclient_redirect }, 544 | { "status", uc_uclient_status }, 545 | 546 | { "read", uc_uclient_read }, 547 | }; 548 | 549 | static const uc_function_list_t global_fns[] = { 550 | { "new", uc_uclient_new }, 551 | }; 552 | 553 | void uc_module_init(uc_vm_t *vm, uc_value_t *scope) 554 | { 555 | uc_uclient_type = uc_type_declare(vm, "uclient", uclient_fns, free_uclient); 556 | registry = ucv_array_new(vm); 557 | uc_vm_registry_set(vm, "uclient.registry", registry); 558 | uc_function_list_register(scope, global_fns); 559 | } 560 | -------------------------------------------------------------------------------- /uclient-fetch.c: -------------------------------------------------------------------------------- 1 | /* 2 | * uclient - ustream based protocol client library 3 | * 4 | * Copyright (C) 2014 Felix Fietkau 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #define _GNU_SOURCE 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | #include "progress.h" 36 | #include "uclient.h" 37 | #include "uclient-utils.h" 38 | 39 | #ifndef strdupa 40 | #define strdupa(x) strcpy(alloca(strlen(x)+1),x) 41 | #endif 42 | 43 | struct header { 44 | struct list_head list; 45 | char *name; 46 | char *value; 47 | }; 48 | 49 | static const char *user_agent = "uclient-fetch"; 50 | static const char *post_data; 51 | static const char *post_file; 52 | static struct ustream_ssl_ctx *ssl_ctx; 53 | static const struct ustream_ssl_ops *ssl_ops; 54 | static int quiet = false; 55 | static bool verify = true; 56 | static bool proxy = true; 57 | static bool default_certs = false; 58 | static bool no_output; 59 | static const char *opt_output_file; 60 | static int output_fd = -1; 61 | static int error_ret; 62 | static off_t out_offset; 63 | static off_t out_bytes; 64 | static off_t out_len; 65 | static char *auth_str; 66 | static char **urls; 67 | static int n_urls; 68 | static int timeout; 69 | static bool resume, cur_resume; 70 | static LIST_HEAD(headers); 71 | 72 | static struct progress pmt; 73 | static struct uloop_timeout pmt_timer; 74 | 75 | static int init_request(struct uclient *cl); 76 | static void request_done(struct uclient *cl); 77 | 78 | static void pmt_update(struct uloop_timeout *t) 79 | { 80 | progress_update(&pmt, out_offset, out_bytes, out_len); 81 | uloop_timeout_set(t, 1000); 82 | } 83 | 84 | static const char * 85 | get_proxy_url(char *url) 86 | { 87 | char prefix[16]; 88 | char *sep; 89 | 90 | if (!proxy) 91 | return NULL; 92 | 93 | sep = strchr(url, ':'); 94 | if (!sep) 95 | return NULL; 96 | 97 | if (sep - url > 5) 98 | return NULL; 99 | 100 | memcpy(prefix, url, sep - url); 101 | strcpy(prefix + (sep - url), "_proxy"); 102 | return getenv(prefix); 103 | } 104 | 105 | static int open_output_file(const char *path, uint64_t resume_offset) 106 | { 107 | const char *output_file = opt_output_file; 108 | char *filename = NULL; 109 | int flags; 110 | int ret; 111 | 112 | if (cur_resume) 113 | flags = O_RDWR; 114 | else 115 | flags = O_WRONLY | O_TRUNC; 116 | 117 | if (!cur_resume && !output_file) 118 | flags |= O_EXCL; 119 | 120 | flags |= O_CREAT; 121 | 122 | if (output_file) { 123 | if (!strcmp(output_file, "-")) { 124 | if (!quiet) 125 | fprintf(stderr, "Writing to stdout\n"); 126 | 127 | ret = STDOUT_FILENO; 128 | goto done; 129 | } 130 | } else { 131 | filename = uclient_get_url_filename(path, "index.html"); 132 | if (!filename) { 133 | ret = -ENOMEM; 134 | goto out; 135 | } 136 | 137 | output_file = filename; 138 | } 139 | 140 | if (!quiet) 141 | fprintf(stderr, "Writing to '%s'\n", output_file); 142 | ret = open(output_file, flags, 0644); 143 | if (ret < 0) 144 | goto free; 145 | 146 | if (resume_offset && 147 | lseek(ret, resume_offset, SEEK_SET) < 0) { 148 | if (!quiet) 149 | fprintf(stderr, "Failed to seek %"PRIu64" bytes in output file\n", resume_offset); 150 | close(ret); 151 | ret = -1; 152 | goto free; 153 | } 154 | 155 | out_offset = resume_offset; 156 | out_bytes += resume_offset; 157 | done: 158 | if (!quiet) { 159 | progress_init(&pmt, output_file); 160 | pmt_timer.cb = pmt_update; 161 | pmt_timer.cb(&pmt_timer); 162 | } 163 | 164 | free: 165 | free(filename); 166 | out: 167 | return ret; 168 | } 169 | 170 | static void header_done_cb(struct uclient *cl) 171 | { 172 | enum { 173 | H_RANGE, 174 | H_LEN, 175 | __H_MAX 176 | }; 177 | static const struct blobmsg_policy policy[__H_MAX] = { 178 | [H_RANGE] = { .name = "content-range", .type = BLOBMSG_TYPE_STRING }, 179 | [H_LEN] = { .name = "content-length", .type = BLOBMSG_TYPE_STRING }, 180 | }; 181 | struct blob_attr *tb[__H_MAX]; 182 | uint64_t resume_offset = 0, resume_end, resume_size; 183 | static int retries; 184 | 185 | if (retries < 10) { 186 | int ret = uclient_http_redirect(cl); 187 | if (ret < 0) { 188 | if (!quiet) 189 | fprintf(stderr, "Failed to redirect to %s on %s\n", cl->url->location, cl->url->host); 190 | error_ret = 8; 191 | request_done(cl); 192 | return; 193 | } 194 | if (ret > 0) { 195 | if (!quiet) 196 | fprintf(stderr, "Redirected to %s on %s\n", cl->url->location, cl->url->host); 197 | 198 | retries++; 199 | return; 200 | } 201 | } 202 | 203 | if (cl->status_code == 204 && cur_resume) { 204 | /* Resume attempt failed, try normal download */ 205 | cur_resume = false; 206 | init_request(cl); 207 | return; 208 | } 209 | 210 | blobmsg_parse(policy, __H_MAX, tb, blob_data(cl->meta), blob_len(cl->meta)); 211 | 212 | switch (cl->status_code) { 213 | case 416: 214 | if (!quiet) 215 | fprintf(stderr, "File download already fully retrieved; nothing to do.\n"); 216 | request_done(cl); 217 | break; 218 | case 206: 219 | if (!cur_resume) { 220 | if (!quiet) 221 | fprintf(stderr, "Error: Partial content received, full content requested\n"); 222 | error_ret = 8; 223 | request_done(cl); 224 | break; 225 | } 226 | 227 | if (!tb[H_RANGE]) { 228 | if (!quiet) 229 | fprintf(stderr, "Content-Range header is missing\n"); 230 | error_ret = 8; 231 | break; 232 | } 233 | 234 | if (sscanf(blobmsg_get_string(tb[H_RANGE]), 235 | "bytes %"PRIu64"-%"PRIu64"/%"PRIu64, 236 | &resume_offset, &resume_end, &resume_size) != 3) { 237 | if (!quiet) 238 | fprintf(stderr, "Content-Range header is invalid\n"); 239 | error_ret = 8; 240 | break; 241 | } 242 | /* fall through */ 243 | case 204: 244 | case 200: 245 | if (no_output) 246 | break; 247 | 248 | if (tb[H_LEN]) 249 | out_len = strtoul(blobmsg_get_string(tb[H_LEN]), NULL, 10); 250 | 251 | output_fd = open_output_file(cl->url->location, resume_offset); 252 | if (output_fd < 0) { 253 | if (!quiet) 254 | perror("Cannot open output file"); 255 | error_ret = 3; 256 | request_done(cl); 257 | } 258 | break; 259 | 260 | default: 261 | if (!quiet) 262 | fprintf(stderr, "HTTP error %d\n", cl->status_code); 263 | request_done(cl); 264 | error_ret = 8; 265 | break; 266 | } 267 | } 268 | 269 | static void read_data_cb(struct uclient *cl) 270 | { 271 | char buf[256]; 272 | ssize_t n; 273 | int len; 274 | 275 | if (!no_output && output_fd < 0) 276 | return; 277 | 278 | while (1) { 279 | len = uclient_read(cl, buf, sizeof(buf)); 280 | if (len <= 0) 281 | return; 282 | 283 | out_bytes += len; 284 | if (!no_output) { 285 | n = write(output_fd, buf, len); 286 | if (n < 0) 287 | return; 288 | } 289 | } 290 | } 291 | 292 | static void msg_connecting(struct uclient *cl) 293 | { 294 | char addr[INET6_ADDRSTRLEN]; 295 | int port; 296 | 297 | if (quiet) 298 | return; 299 | 300 | uclient_get_addr(addr, &port, &cl->remote_addr); 301 | fprintf(stderr, "Connecting to %s:%d\n", addr, port); 302 | } 303 | 304 | static void check_resume_offset(struct uclient *cl) 305 | { 306 | char range_str[64]; 307 | struct stat st; 308 | char *file; 309 | int ret; 310 | 311 | file = uclient_get_url_filename(cl->url->location, "index.html"); 312 | if (!file) 313 | return; 314 | 315 | ret = stat(file, &st); 316 | free(file); 317 | if (ret) 318 | return; 319 | 320 | if (!st.st_size) 321 | return; 322 | 323 | snprintf(range_str, sizeof(range_str), "bytes=%"PRIu64"-", (uint64_t) st.st_size); 324 | uclient_http_set_header(cl, "Range", range_str); 325 | } 326 | 327 | static int init_request(struct uclient *cl) 328 | { 329 | struct header *h; 330 | char *content_type = "application/x-www-form-urlencoded"; 331 | int rc; 332 | 333 | out_offset = 0; 334 | out_bytes = 0; 335 | out_len = 0; 336 | uclient_http_set_ssl_ctx(cl, ssl_ops, ssl_ctx, verify); 337 | 338 | if (timeout) 339 | cl->timeout_msecs = timeout * 1000; 340 | 341 | rc = uclient_connect(cl); 342 | if (rc) 343 | return rc; 344 | 345 | msg_connecting(cl); 346 | 347 | rc = uclient_http_set_request_type(cl, post_data || post_file ? "POST" : "GET"); 348 | if (rc) 349 | return rc; 350 | 351 | uclient_http_reset_headers(cl); 352 | 353 | list_for_each_entry(h, &headers, list) { 354 | if (!strcasecmp(h->name, "Content-Type")) { 355 | if (!post_data && !post_file) 356 | return -EINVAL; 357 | 358 | content_type = h->value; 359 | } else if (!strcasecmp(h->name, "User-Agent")) { 360 | user_agent = h->value; 361 | } else { 362 | uclient_http_set_header(cl, h->name, h->value); 363 | } 364 | } 365 | 366 | uclient_http_set_header(cl, "User-Agent", user_agent); 367 | 368 | if (cur_resume) 369 | check_resume_offset(cl); 370 | 371 | if (post_data) { 372 | uclient_http_set_header(cl, "Content-Type", content_type); 373 | uclient_write(cl, post_data, strlen(post_data)); 374 | } 375 | else if(post_file) 376 | { 377 | FILE *input_file; 378 | uclient_http_set_header(cl, "Content-Type", content_type); 379 | 380 | input_file = fopen(post_file, "r"); 381 | if (!input_file) 382 | return errno; 383 | 384 | char tbuf[1024]; 385 | size_t rlen = 0; 386 | do 387 | { 388 | rlen = fread(tbuf, 1, sizeof(tbuf), input_file); 389 | uclient_write(cl, tbuf, rlen); 390 | } 391 | while(rlen); 392 | 393 | fclose(input_file); 394 | } 395 | 396 | rc = uclient_request(cl); 397 | if (rc) 398 | return rc; 399 | 400 | return 0; 401 | } 402 | 403 | static void request_done(struct uclient *cl) 404 | { 405 | const char *proxy_url; 406 | 407 | if (n_urls) { 408 | proxy_url = get_proxy_url(*urls); 409 | if (proxy_url) { 410 | uclient_set_url(cl, proxy_url, NULL); 411 | uclient_set_proxy_url(cl, *urls, auth_str); 412 | } else { 413 | uclient_set_url(cl, *urls, auth_str); 414 | } 415 | n_urls--; 416 | cur_resume = resume; 417 | error_ret = init_request(cl); 418 | if (error_ret == 0) 419 | return; 420 | } 421 | 422 | if (output_fd >= 0 && !opt_output_file) { 423 | close(output_fd); 424 | output_fd = -1; 425 | } 426 | uclient_disconnect(cl); 427 | uloop_end(); 428 | } 429 | 430 | 431 | static void eof_cb(struct uclient *cl) 432 | { 433 | if (!quiet) { 434 | pmt_update(&pmt_timer); 435 | uloop_timeout_cancel(&pmt_timer); 436 | fprintf(stderr, "\n"); 437 | } 438 | 439 | if (!cl->data_eof) { 440 | if (!quiet) 441 | fprintf(stderr, "Connection reset prematurely\n"); 442 | error_ret = 4; 443 | } else if (!quiet) { 444 | fprintf(stderr, "Download completed (%"PRIu64" bytes)\n", (uint64_t) out_bytes); 445 | } 446 | request_done(cl); 447 | } 448 | 449 | static void 450 | handle_uclient_log_msg(struct uclient *cl, enum uclient_log_type type, const char *msg) 451 | { 452 | static const char * const type_str_list[] = { 453 | [UCLIENT_LOG_SSL_ERROR] = "SSL error", 454 | [UCLIENT_LOG_SSL_VERIFY_ERROR] = "SSL verify error", 455 | }; 456 | const char *type_str = NULL; 457 | 458 | if (type < ARRAY_SIZE(type_str_list)) 459 | type_str = type_str_list[type]; 460 | if (!type_str) 461 | type_str = "Unknown"; 462 | 463 | fprintf(stderr, "%s: %s\n", type_str, msg); 464 | } 465 | 466 | static void handle_uclient_error(struct uclient *cl, int code) 467 | { 468 | const char *type = "Unknown error"; 469 | bool ignore = false; 470 | 471 | switch(code) { 472 | case UCLIENT_ERROR_CONNECT: 473 | type = "Connection failed"; 474 | error_ret = 4; 475 | break; 476 | case UCLIENT_ERROR_TIMEDOUT: 477 | type = "Connection timed out"; 478 | error_ret = 4; 479 | break; 480 | case UCLIENT_ERROR_SSL_INVALID_CERT: 481 | type = "Invalid SSL certificate"; 482 | ignore = !verify; 483 | error_ret = 5; 484 | break; 485 | case UCLIENT_ERROR_SSL_CN_MISMATCH: 486 | type = "Server hostname does not match SSL certificate"; 487 | ignore = !verify; 488 | error_ret = 5; 489 | break; 490 | default: 491 | error_ret = 1; 492 | break; 493 | } 494 | 495 | if (!quiet) 496 | fprintf(stderr, "Connection error: %s%s\n", type, ignore ? " (ignored)" : ""); 497 | 498 | if (ignore) 499 | error_ret = 0; 500 | else 501 | request_done(cl); 502 | } 503 | 504 | static const struct uclient_cb cb = { 505 | .header_done = header_done_cb, 506 | .data_read = read_data_cb, 507 | .data_eof = eof_cb, 508 | .error = handle_uclient_error, 509 | .log_msg = handle_uclient_log_msg, 510 | }; 511 | 512 | static void usage(const char *progname) 513 | { 514 | fprintf(stderr, 515 | "Usage: %s [options] \n" 516 | "Options:\n" 517 | " -4 Use IPv4 only\n" 518 | " -6 Use IPv6 only\n" 519 | " -O Redirect output to file (use \"-\" for stdout)\n" 520 | " -P Set directory for output files\n" 521 | " --quiet | -q Turn off status messages\n" 522 | " --continue | -c Continue a partially-downloaded file\n" 523 | " --header='Header: value' Add HTTP header. Multiple allowed\n" 524 | " --user= HTTP authentication username\n" 525 | " --password= HTTP authentication password\n" 526 | " --user-agent | -U Set HTTP user agent\n" 527 | " --post-data=STRING use the POST method; send STRING as the data\n" 528 | " --post-file=FILE use the POST method; send FILE as the data\n" 529 | " --spider | -s Spider mode - only check file existence\n" 530 | " --timeout=N | -T N Set connect/request timeout to N seconds\n" 531 | " --proxy=on | -Y on Enable interpretation of proxy env vars (default)\n" 532 | " --proxy=off | -Y off |\n" 533 | " --no-proxy Disable interpretation of proxy env vars\n" 534 | "\n" 535 | "HTTPS options:\n" 536 | " --ca-certificate= Load CA certificates from file \n" 537 | " --no-check-certificate don't validate the server's certificate\n" 538 | " --ciphers= Set the cipher list string\n" 539 | "\n", progname); 540 | error_ret = 1; 541 | } 542 | 543 | static void init_ca_cert(void) 544 | { 545 | glob_t gl; 546 | unsigned int i; 547 | 548 | glob("/etc/ssl/certs/*.crt", 0, NULL, &gl); 549 | for (i = 0; i < gl.gl_pathc; i++) 550 | ssl_ops->context_add_ca_crt_file(ssl_ctx, gl.gl_pathv[i]); 551 | globfree(&gl); 552 | } 553 | 554 | static void no_ssl(const char *progname) 555 | { 556 | fprintf(stderr, 557 | "%s: SSL support not available, please install one of the " 558 | "libustream-.*[ssl|tls] packages as well as the ca-bundle and " 559 | "ca-certificates packages.\n", 560 | progname); 561 | error_ret = 1; 562 | } 563 | 564 | static void debug_cb(void *priv, int level, const char *msg) 565 | { 566 | fprintf(stderr, "%s\n", msg); 567 | } 568 | 569 | static bool is_valid_header(char *str) 570 | { 571 | char *tmp = str; 572 | 573 | /* First character must be a letter */ 574 | if (!isalpha(*tmp)) 575 | return false; 576 | 577 | /* Subsequent characters must be letters, numbers or dashes */ 578 | while (*(++tmp) != '\0') { 579 | if (!isalnum(*tmp) && *tmp != '-') 580 | return false; 581 | } 582 | 583 | return true; 584 | }; 585 | 586 | enum { 587 | L_NO_CHECK_CERTIFICATE, 588 | L_CA_CERTIFICATE, 589 | L_CIPHERS, 590 | L_USER, 591 | L_PASSWORD, 592 | L_USER_AGENT, 593 | L_POST_DATA, 594 | L_POST_FILE, 595 | L_SPIDER, 596 | L_TIMEOUT, 597 | L_CONTINUE, 598 | L_PROXY, 599 | L_NO_PROXY, 600 | L_QUIET, 601 | L_VERBOSE, 602 | L_HEADER, 603 | }; 604 | 605 | static const struct option longopts[] = { 606 | [L_NO_CHECK_CERTIFICATE] = { "no-check-certificate", no_argument, NULL, 0 }, 607 | [L_CA_CERTIFICATE] = { "ca-certificate", required_argument, NULL, 0 }, 608 | [L_CIPHERS] = { "ciphers", required_argument, NULL, 0 }, 609 | [L_USER] = { "user", required_argument, NULL, 0 }, 610 | [L_PASSWORD] = { "password", required_argument, NULL, 0 }, 611 | [L_USER_AGENT] = { "user-agent", required_argument, NULL, 0 }, 612 | [L_POST_DATA] = { "post-data", required_argument, NULL, 0 }, 613 | [L_POST_FILE] = { "post-file", required_argument, NULL, 0 }, 614 | [L_SPIDER] = { "spider", no_argument, NULL, 0 }, 615 | [L_TIMEOUT] = { "timeout", required_argument, NULL, 0 }, 616 | [L_CONTINUE] = { "continue", no_argument, NULL, 0 }, 617 | [L_PROXY] = { "proxy", required_argument, NULL, 0 }, 618 | [L_NO_PROXY] = { "no-proxy", no_argument, NULL, 0 }, 619 | [L_QUIET] = { "quiet", no_argument, NULL, 0 }, 620 | [L_VERBOSE] = { "verbose", no_argument, NULL, 0 }, 621 | [L_HEADER] = { "header", required_argument, NULL, 0 }, 622 | {} 623 | }; 624 | 625 | 626 | 627 | int main(int argc, char **argv) 628 | { 629 | const char *progname = argv[0]; 630 | const char *proxy_url; 631 | char *username = NULL; 632 | char *password = NULL; 633 | struct uclient *cl = NULL; 634 | int longopt_idx = 0; 635 | bool has_cert = false; 636 | struct header *h, *th; 637 | char *tmp; 638 | int i, ch; 639 | int rc; 640 | int af = -1; 641 | int debug_level = 0; 642 | 643 | signal(SIGPIPE, SIG_IGN); 644 | ssl_ctx = uclient_new_ssl_context(&ssl_ops); 645 | 646 | while ((ch = getopt_long(argc, argv, "46cO:P:qsT:U:vY:", longopts, &longopt_idx)) != -1) { 647 | switch(ch) { 648 | case 0: 649 | switch (longopt_idx) { 650 | case L_NO_CHECK_CERTIFICATE: 651 | verify = false; 652 | if (ssl_ctx) 653 | ssl_ops->context_set_require_validation(ssl_ctx, verify); 654 | break; 655 | case L_CA_CERTIFICATE: 656 | has_cert = true; 657 | if (ssl_ctx) 658 | ssl_ops->context_add_ca_crt_file(ssl_ctx, optarg); 659 | break; 660 | case L_CIPHERS: 661 | if (ssl_ctx) { 662 | if (ssl_ops->context_set_ciphers(ssl_ctx, optarg)) { 663 | if (!quiet) 664 | fprintf(stderr, "No recognized ciphers in cipher list\n"); 665 | exit(1); 666 | } 667 | } 668 | break; 669 | case L_USER: 670 | if (!strlen(optarg)) 671 | break; 672 | username = strdupa(optarg); 673 | memset(optarg, '*', strlen(optarg)); 674 | break; 675 | case L_PASSWORD: 676 | if (!strlen(optarg)) 677 | break; 678 | password = strdupa(optarg); 679 | memset(optarg, '*', strlen(optarg)); 680 | break; 681 | case L_USER_AGENT: 682 | user_agent = optarg; 683 | break; 684 | case L_POST_DATA: 685 | post_data = optarg; 686 | break; 687 | case L_POST_FILE: 688 | post_file = optarg; 689 | break; 690 | case L_SPIDER: 691 | no_output = true; 692 | break; 693 | case L_TIMEOUT: 694 | timeout = atoi(optarg); 695 | break; 696 | case L_CONTINUE: 697 | resume = true; 698 | break; 699 | case L_PROXY: 700 | if (strcmp(optarg, "on") != 0) 701 | proxy = false; 702 | break; 703 | case L_NO_PROXY: 704 | proxy = false; 705 | break; 706 | case L_QUIET: 707 | quiet = true; 708 | break; 709 | case L_VERBOSE: 710 | debug_level++; 711 | break; 712 | case L_HEADER: 713 | tmp = strchr(optarg, ':'); 714 | if (!tmp) { 715 | usage(progname); 716 | goto out; 717 | } 718 | *(tmp++) = '\0'; 719 | while (isspace(*tmp)) 720 | ++tmp; 721 | 722 | if (*tmp == '\0' || !is_valid_header(optarg) || strchr(tmp, '\n')) { 723 | usage(progname); 724 | goto out; 725 | } 726 | h = malloc(sizeof(*h)); 727 | if (!h) { 728 | perror("Set HTTP header"); 729 | error_ret = 1; 730 | goto out; 731 | } 732 | h->name = optarg; 733 | h->value = tmp; 734 | list_add_tail(&h->list, &headers); 735 | break; 736 | default: 737 | usage(progname); 738 | goto out; 739 | } 740 | break; 741 | case '4': 742 | af = AF_INET; 743 | break; 744 | case '6': 745 | af = AF_INET6; 746 | break; 747 | case 'c': 748 | resume = true; 749 | break; 750 | case 'U': 751 | user_agent = optarg; 752 | break; 753 | case 'O': 754 | opt_output_file = optarg; 755 | break; 756 | case 'P': 757 | if (chdir(optarg)) { 758 | if (!quiet) 759 | perror("Change output directory"); 760 | error_ret = 1; 761 | goto out; 762 | } 763 | break; 764 | case 'q': 765 | quiet = true; 766 | break; 767 | case 's': 768 | no_output = true; 769 | break; 770 | case 'T': 771 | timeout = atoi(optarg); 772 | break; 773 | case 'v': 774 | debug_level++; 775 | break; 776 | case 'Y': 777 | if (strcmp(optarg, "on") != 0) 778 | proxy = false; 779 | break; 780 | default: 781 | usage(progname); 782 | goto out; 783 | } 784 | } 785 | 786 | argv += optind; 787 | argc -= optind; 788 | 789 | if (debug_level) 790 | ssl_ops->context_set_debug(ssl_ctx, debug_level, debug_cb, NULL); 791 | 792 | if (verify && !has_cert) 793 | default_certs = true; 794 | 795 | if (argc < 1) { 796 | usage(progname); 797 | goto out; 798 | } 799 | 800 | if (!ssl_ctx) { 801 | for (i = 0; i < argc; i++) { 802 | if (!strncmp(argv[i], "https", 5)) { 803 | no_ssl(progname); 804 | goto out; 805 | } 806 | } 807 | } 808 | 809 | urls = argv + 1; 810 | n_urls = argc - 1; 811 | 812 | uloop_init(); 813 | 814 | if (username) { 815 | if (password) { 816 | rc = asprintf(&auth_str, "%s:%s", username, password); 817 | if (rc < 0) { 818 | error_ret = 1; 819 | goto out; 820 | } 821 | } else 822 | auth_str = username; 823 | } 824 | 825 | if (!quiet) 826 | fprintf(stderr, "Downloading '%s'\n", argv[0]); 827 | 828 | proxy_url = get_proxy_url(argv[0]); 829 | if (proxy_url) { 830 | cl = uclient_new(proxy_url, auth_str, &cb); 831 | if (cl) 832 | uclient_set_proxy_url(cl, argv[0], NULL); 833 | } else { 834 | cl = uclient_new(argv[0], auth_str, &cb); 835 | } 836 | if (!cl) { 837 | fprintf(stderr, "Failed to allocate uclient context\n"); 838 | error_ret = 1; 839 | goto out; 840 | } 841 | if (af >= 0) 842 | uclient_http_set_address_family(cl, af); 843 | 844 | if (ssl_ctx && default_certs) 845 | init_ca_cert(); 846 | 847 | cur_resume = resume; 848 | rc = init_request(cl); 849 | if (!rc) { 850 | /* no error received, we can enter main loop */ 851 | uloop_run(); 852 | } else { 853 | fprintf(stderr, "Failed to send request: %s\n", strerror(rc)); 854 | error_ret = 4; 855 | goto out; 856 | } 857 | 858 | uloop_done(); 859 | 860 | out: 861 | if (cl) 862 | uclient_free(cl); 863 | 864 | if (output_fd >= 0 && output_fd != STDOUT_FILENO) 865 | close(output_fd); 866 | 867 | if (ssl_ctx) 868 | ssl_ops->context_free(ssl_ctx); 869 | 870 | list_for_each_entry_safe(h, th, &headers, list) { 871 | list_del(&h->list); 872 | free(h); 873 | } 874 | 875 | return error_ret; 876 | } 877 | -------------------------------------------------------------------------------- /uclient-http.c: -------------------------------------------------------------------------------- 1 | /* 2 | * uclient - ustream based protocol client library 3 | * 4 | * Copyright (C) 2014 Felix Fietkau 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "uclient.h" 32 | #include "uclient-utils.h" 33 | #include "uclient-backend.h" 34 | 35 | enum auth_type { 36 | AUTH_TYPE_UNKNOWN, 37 | AUTH_TYPE_NONE, 38 | AUTH_TYPE_BASIC, 39 | AUTH_TYPE_DIGEST, 40 | }; 41 | 42 | enum request_type { 43 | REQ_GET, 44 | REQ_HEAD, 45 | REQ_POST, 46 | REQ_PUT, 47 | REQ_DELETE, 48 | __REQ_MAX 49 | }; 50 | 51 | enum http_state { 52 | HTTP_STATE_INIT, 53 | HTTP_STATE_HEADERS_SENT, 54 | HTTP_STATE_REQUEST_DONE, 55 | HTTP_STATE_RECV_HEADERS, 56 | HTTP_STATE_RECV_DATA, 57 | HTTP_STATE_ERROR, 58 | }; 59 | 60 | static const char * const request_types[__REQ_MAX] = { 61 | [REQ_GET] = "GET", 62 | [REQ_HEAD] = "HEAD", 63 | [REQ_POST] = "POST", 64 | [REQ_PUT] = "PUT", 65 | [REQ_DELETE] = "DELETE", 66 | }; 67 | 68 | struct uclient_http { 69 | struct uclient uc; 70 | 71 | const struct ustream_ssl_ops *ssl_ops; 72 | struct ustream_ssl_ctx *ssl_ctx; 73 | struct ustream *us; 74 | 75 | union { 76 | struct ustream_fd ufd; 77 | struct ustream_ssl ussl; 78 | }; 79 | 80 | struct uloop_timeout disconnect_t; 81 | unsigned int seq; 82 | int fd; 83 | 84 | bool ssl_require_validation; 85 | bool ssl; 86 | bool eof; 87 | bool connection_close; 88 | bool disconnect; 89 | enum request_type req_type; 90 | enum http_state state; 91 | 92 | enum auth_type auth_type; 93 | char *auth_str; 94 | 95 | long read_chunked; 96 | long content_length; 97 | 98 | int usock_flags; 99 | 100 | uint32_t nc; 101 | 102 | struct blob_buf headers; 103 | struct blob_buf meta; 104 | }; 105 | 106 | enum { 107 | PREFIX_HTTP, 108 | PREFIX_HTTPS, 109 | __PREFIX_MAX, 110 | }; 111 | 112 | static const char * const uclient_http_prefix[] = { 113 | [PREFIX_HTTP] = "http://", 114 | [PREFIX_HTTPS] = "https://", 115 | [__PREFIX_MAX] = NULL 116 | }; 117 | 118 | static int uclient_http_connect(struct uclient *cl); 119 | 120 | static int uclient_do_connect(struct uclient_http *uh, const char *port) 121 | { 122 | socklen_t sl; 123 | int fd; 124 | 125 | if (uh->uc.url->port) 126 | port = uh->uc.url->port; 127 | 128 | memset(&uh->uc.remote_addr, 0, sizeof(uh->uc.remote_addr)); 129 | 130 | fd = usock_inet_timeout(USOCK_TCP | USOCK_NONBLOCK | uh->usock_flags, 131 | uh->uc.url->host, port, &uh->uc.remote_addr, 132 | uh->uc.timeout_msecs); 133 | if (fd < 0) 134 | return -1; 135 | 136 | fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); 137 | uh->fd = fd; 138 | 139 | sl = sizeof(uh->uc.local_addr); 140 | memset(&uh->uc.local_addr, 0, sl); 141 | getsockname(fd, &uh->uc.local_addr.sa, &sl); 142 | 143 | return 0; 144 | } 145 | 146 | static void uclient_http_disconnect(struct uclient_http *uh) 147 | { 148 | uloop_timeout_cancel(&uh->disconnect_t); 149 | if (!uh->us) 150 | return; 151 | 152 | if (uh->ssl) 153 | ustream_free(&uh->ussl.stream); 154 | else 155 | ustream_free(&uh->ufd.stream); 156 | if(uh->fd >= 0) 157 | close(uh->fd); 158 | uh->us = NULL; 159 | } 160 | 161 | static void uclient_http_free_url_state(struct uclient *cl) 162 | { 163 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 164 | 165 | uh->auth_type = AUTH_TYPE_UNKNOWN; 166 | free(uh->auth_str); 167 | uh->auth_str = NULL; 168 | uclient_http_disconnect(uh); 169 | } 170 | 171 | static void uclient_http_error(struct uclient_http *uh, int code) 172 | { 173 | uh->state = HTTP_STATE_ERROR; 174 | uh->us->eof = true; 175 | ustream_state_change(uh->us); 176 | uclient_backend_set_error(&uh->uc, code); 177 | } 178 | 179 | static void uclient_http_request_disconnect(struct uclient *cl) 180 | { 181 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 182 | 183 | if (!uh->us) 184 | return; 185 | 186 | uh->eof = true; 187 | uh->disconnect = true; 188 | uloop_timeout_set(&uh->disconnect_t, 1); 189 | } 190 | 191 | static void uclient_notify_eof(struct uclient_http *uh) 192 | { 193 | struct ustream *us = uh->us; 194 | 195 | if (uh->disconnect) 196 | return; 197 | 198 | if (!uh->eof) { 199 | if (!us->eof && !us->write_error) 200 | return; 201 | 202 | if (ustream_pending_data(us, false)) 203 | return; 204 | } 205 | 206 | if ((uh->content_length < 0 && uh->read_chunked >= 0) || 207 | uh->content_length == 0) 208 | uh->uc.data_eof = true; 209 | 210 | uclient_backend_set_eof(&uh->uc); 211 | 212 | if (uh->connection_close) 213 | uclient_http_request_disconnect(&uh->uc); 214 | } 215 | 216 | static void uclient_http_reset_state(struct uclient_http *uh) 217 | { 218 | uh->seq++; 219 | uclient_backend_reset_state(&uh->uc); 220 | uh->read_chunked = -1; 221 | uh->content_length = -1; 222 | uh->eof = false; 223 | uh->disconnect = false; 224 | uh->connection_close = false; 225 | uh->state = HTTP_STATE_INIT; 226 | 227 | if (uh->auth_type == AUTH_TYPE_UNKNOWN && !uh->uc.url->auth) 228 | uh->auth_type = AUTH_TYPE_NONE; 229 | } 230 | 231 | static void uclient_http_init_request(struct uclient_http *uh) 232 | { 233 | uh->seq++; 234 | uclient_http_reset_state(uh); 235 | blob_buf_init(&uh->meta, 0); 236 | } 237 | 238 | static enum auth_type 239 | uclient_http_update_auth_type(struct uclient_http *uh) 240 | { 241 | if (!uh->auth_str) 242 | return AUTH_TYPE_NONE; 243 | 244 | if (!strncasecmp(uh->auth_str, "basic", 5)) 245 | return AUTH_TYPE_BASIC; 246 | 247 | if (!strncasecmp(uh->auth_str, "digest", 6)) 248 | return AUTH_TYPE_DIGEST; 249 | 250 | return AUTH_TYPE_NONE; 251 | } 252 | 253 | static void uclient_http_process_headers(struct uclient_http *uh) 254 | { 255 | enum { 256 | HTTP_HDR_TRANSFER_ENCODING, 257 | HTTP_HDR_CONNECTION, 258 | HTTP_HDR_CONTENT_LENGTH, 259 | HTTP_HDR_AUTH, 260 | __HTTP_HDR_MAX, 261 | }; 262 | static const struct blobmsg_policy hdr_policy[__HTTP_HDR_MAX] = { 263 | #define hdr(_name) { .name = _name, .type = BLOBMSG_TYPE_STRING } 264 | [HTTP_HDR_TRANSFER_ENCODING] = hdr("transfer-encoding"), 265 | [HTTP_HDR_CONNECTION] = hdr("connection"), 266 | [HTTP_HDR_CONTENT_LENGTH] = hdr("content-length"), 267 | [HTTP_HDR_AUTH] = hdr("www-authenticate"), 268 | #undef hdr 269 | }; 270 | struct blob_attr *tb[__HTTP_HDR_MAX]; 271 | struct blob_attr *cur; 272 | 273 | blobmsg_parse(hdr_policy, __HTTP_HDR_MAX, tb, blob_data(uh->meta.head), blob_len(uh->meta.head)); 274 | 275 | cur = tb[HTTP_HDR_TRANSFER_ENCODING]; 276 | if (cur && strstr(blobmsg_data(cur), "chunked")) 277 | uh->read_chunked = 0; 278 | 279 | cur = tb[HTTP_HDR_CONNECTION]; 280 | if (cur && strstr(blobmsg_data(cur), "close")) 281 | uh->connection_close = true; 282 | 283 | cur = tb[HTTP_HDR_CONTENT_LENGTH]; 284 | if (cur) 285 | uh->content_length = strtoul(blobmsg_data(cur), NULL, 10); 286 | 287 | cur = tb[HTTP_HDR_AUTH]; 288 | if (cur) { 289 | free(uh->auth_str); 290 | uh->auth_str = strdup(blobmsg_data(cur)); 291 | } 292 | 293 | uh->auth_type = uclient_http_update_auth_type(uh); 294 | } 295 | 296 | static bool uclient_request_supports_body(enum request_type req_type) 297 | { 298 | switch (req_type) { 299 | case REQ_POST: 300 | case REQ_PUT: 301 | case REQ_DELETE: 302 | return true; 303 | default: 304 | return false; 305 | } 306 | } 307 | 308 | static int 309 | uclient_http_add_auth_basic(struct uclient_http *uh) 310 | { 311 | struct uclient_url *url = uh->uc.url; 312 | int auth_len = strlen(url->auth); 313 | char *auth_buf; 314 | 315 | if (auth_len > 512) 316 | return -EINVAL; 317 | 318 | auth_buf = alloca(base64_len(auth_len) + 1); 319 | if (!auth_buf) 320 | return -ENOMEM; 321 | 322 | base64_encode(url->auth, auth_len, auth_buf); 323 | ustream_printf(uh->us, "Authorization: Basic %s\r\n", auth_buf); 324 | 325 | return 0; 326 | } 327 | 328 | static char *digest_unquote_sep(char **str) 329 | { 330 | char *cur = *str + 1; 331 | char *start = cur; 332 | char *out; 333 | 334 | if (**str != '"') 335 | return NULL; 336 | 337 | out = cur; 338 | while (1) { 339 | if (!*cur) 340 | return NULL; 341 | 342 | if (*cur == '"') { 343 | cur++; 344 | break; 345 | } 346 | 347 | if (*cur == '\\') 348 | cur++; 349 | 350 | *(out++) = *(cur++); 351 | } 352 | 353 | if (*cur == ',') 354 | cur++; 355 | 356 | *out = 0; 357 | *str = cur; 358 | 359 | return start; 360 | } 361 | 362 | static char *digest_sep(char **str) 363 | { 364 | char *cur, *next; 365 | 366 | cur = *str; 367 | next = strchr(*str, ','); 368 | if (next) { 369 | *str = next + 1; 370 | *next = 0; 371 | } else { 372 | *str += strlen(*str); 373 | } 374 | 375 | return cur; 376 | } 377 | 378 | static bool strmatch(char **str, const char *prefix) 379 | { 380 | int len = strlen(prefix); 381 | 382 | if (strncmp(*str, prefix, len) != 0 || (*str)[len] != '=') 383 | return false; 384 | 385 | *str += len + 1; 386 | return true; 387 | } 388 | 389 | static void 390 | get_cnonce(char *dest) 391 | { 392 | uint32_t val = 0; 393 | FILE *f; 394 | size_t n; 395 | 396 | f = fopen("/dev/urandom", "r"); 397 | if (f) { 398 | n = fread(&val, sizeof(val), 1, f); 399 | fclose(f); 400 | if (n != 1) 401 | return; 402 | } 403 | 404 | bin_to_hex(dest, &val, sizeof(val)); 405 | } 406 | 407 | static void add_field(char **buf, int *ofs, int *len, const char *name, const char *val) 408 | { 409 | int available = *len - *ofs; 410 | int required; 411 | const char *next; 412 | char *cur; 413 | 414 | if (*len && !*buf) 415 | return; 416 | 417 | required = strlen(name) + 4 + strlen(val) * 2; 418 | if (required > available) 419 | *len += required - available + 64; 420 | 421 | *buf = realloc(*buf, *len); 422 | if (!*buf) 423 | return; 424 | 425 | cur = *buf + *ofs; 426 | cur += sprintf(cur, ", %s=\"", name); 427 | 428 | while ((next = strchr(val, '"'))) { 429 | if (next > val) { 430 | memcpy(cur, val, next - val); 431 | cur += next - val; 432 | } 433 | 434 | cur += sprintf(cur, "\\\""); 435 | val = next + 1; 436 | } 437 | 438 | cur += sprintf(cur, "%s\"", val); 439 | *ofs = cur - *buf; 440 | } 441 | 442 | static int 443 | uclient_http_add_auth_digest(struct uclient_http *uh) 444 | { 445 | struct uclient_url *url = uh->uc.url; 446 | const char *realm = NULL, *opaque = NULL; 447 | const char *user, *password; 448 | char *buf, *next; 449 | int len, ofs; 450 | int err = 0; 451 | 452 | char cnonce_str[9]; 453 | char nc_str[9]; 454 | char ahash[33]; 455 | char hash[33]; 456 | 457 | struct http_digest_data data = { 458 | .nc = nc_str, 459 | .cnonce = cnonce_str, 460 | .auth_hash = ahash, 461 | }; 462 | 463 | len = strlen(uh->auth_str) + 1; 464 | if (len > 512) { 465 | err = -EINVAL; 466 | goto fail; 467 | } 468 | 469 | buf = alloca(len); 470 | if (!buf) { 471 | err = -ENOMEM; 472 | goto fail; 473 | } 474 | 475 | strcpy(buf, uh->auth_str); 476 | 477 | /* skip auth type */ 478 | strsep(&buf, " "); 479 | 480 | next = buf; 481 | while (*next) { 482 | const char **dest = NULL; 483 | const char *tmp; 484 | 485 | while (*next && isspace(*next)) 486 | next++; 487 | 488 | if (strmatch(&next, "realm")) 489 | dest = &realm; 490 | else if (strmatch(&next, "qop")) 491 | dest = &data.qop; 492 | else if (strmatch(&next, "nonce")) 493 | dest = &data.nonce; 494 | else if (strmatch(&next, "opaque")) 495 | dest = &opaque; 496 | else if (strmatch(&next, "stale") || 497 | strmatch(&next, "algorithm") || 498 | strmatch(&next, "auth-param")) { 499 | digest_sep(&next); 500 | continue; 501 | } else if (strmatch(&next, "domain") || 502 | strmatch(&next, "qop-options")) 503 | dest = &tmp; 504 | else { 505 | digest_sep(&next); 506 | continue; 507 | } 508 | 509 | *dest = digest_unquote_sep(&next); 510 | } 511 | 512 | if (!realm || !data.qop || !data.nonce) { 513 | err = -EINVAL; 514 | goto fail; 515 | } 516 | 517 | sprintf(nc_str, "%08x", uh->nc++); 518 | get_cnonce(cnonce_str); 519 | 520 | data.qop = "auth"; 521 | data.uri = url->location; 522 | data.method = request_types[uh->req_type]; 523 | 524 | password = strchr(url->auth, ':'); 525 | if (password) { 526 | char *user_buf; 527 | 528 | len = password - url->auth; 529 | if (len > 256) { 530 | err = -EINVAL; 531 | goto fail; 532 | } 533 | 534 | user_buf = alloca(len + 1); 535 | if (!user_buf) { 536 | err = -ENOMEM; 537 | goto fail; 538 | } 539 | 540 | strncpy(user_buf, url->auth, len); 541 | user_buf[len] = 0; 542 | user = user_buf; 543 | password++; 544 | } else { 545 | user = url->auth; 546 | password = ""; 547 | } 548 | 549 | http_digest_calculate_auth_hash(ahash, user, realm, password); 550 | http_digest_calculate_response(hash, &data); 551 | 552 | buf = NULL; 553 | len = 0; 554 | ofs = 0; 555 | 556 | add_field(&buf, &ofs, &len, "username", user); 557 | add_field(&buf, &ofs, &len, "realm", realm); 558 | add_field(&buf, &ofs, &len, "nonce", data.nonce); 559 | add_field(&buf, &ofs, &len, "uri", data.uri); 560 | add_field(&buf, &ofs, &len, "cnonce", data.cnonce); 561 | add_field(&buf, &ofs, &len, "response", hash); 562 | if (opaque) 563 | add_field(&buf, &ofs, &len, "opaque", opaque); 564 | 565 | ustream_printf(uh->us, "Authorization: Digest nc=%s, qop=%s%s\r\n", data.nc, data.qop, buf); 566 | 567 | free(buf); 568 | 569 | return 0; 570 | 571 | fail: 572 | return err; 573 | } 574 | 575 | static int 576 | uclient_http_add_auth_header(struct uclient_http *uh) 577 | { 578 | if (!uh->uc.url->auth) 579 | return 0; 580 | 581 | switch (uh->auth_type) { 582 | case AUTH_TYPE_UNKNOWN: 583 | case AUTH_TYPE_NONE: 584 | break; 585 | case AUTH_TYPE_BASIC: 586 | return uclient_http_add_auth_basic(uh); 587 | case AUTH_TYPE_DIGEST: 588 | return uclient_http_add_auth_digest(uh); 589 | } 590 | 591 | return 0; 592 | } 593 | 594 | static int 595 | uclient_http_send_headers(struct uclient_http *uh) 596 | { 597 | struct uclient_url *url = uh->uc.url; 598 | struct blob_attr *cur; 599 | enum request_type req_type = uh->req_type; 600 | bool literal_ipv6; 601 | int err; 602 | size_t rem; 603 | 604 | if (uh->state >= HTTP_STATE_HEADERS_SENT) 605 | return 0; 606 | 607 | if (uh->uc.proxy_url) 608 | url = uh->uc.proxy_url; 609 | 610 | literal_ipv6 = strchr(url->host, ':'); 611 | 612 | ustream_printf(uh->us, 613 | "%s %s HTTP/1.1\r\n" 614 | "Host: %s%s%s%s%s\r\n", 615 | request_types[req_type], url->location, 616 | literal_ipv6 ? "[" : "", 617 | url->host, 618 | literal_ipv6 ? "]" : "", 619 | url->port ? ":" : "", 620 | url->port ? url->port : ""); 621 | 622 | blobmsg_for_each_attr(cur, uh->headers.head, rem) 623 | ustream_printf(uh->us, "%s: %s\r\n", blobmsg_name(cur), (char *) blobmsg_data(cur)); 624 | 625 | if (uclient_request_supports_body(uh->req_type)) 626 | ustream_printf(uh->us, "Transfer-Encoding: chunked\r\n"); 627 | 628 | err = uclient_http_add_auth_header(uh); 629 | if (err) 630 | return err; 631 | 632 | ustream_printf(uh->us, "\r\n"); 633 | 634 | uh->state = HTTP_STATE_HEADERS_SENT; 635 | 636 | return 0; 637 | } 638 | 639 | static void uclient_http_headers_complete(struct uclient_http *uh) 640 | { 641 | enum auth_type auth_type = uh->auth_type; 642 | int seq = uh->uc.seq; 643 | 644 | uh->state = HTTP_STATE_RECV_DATA; 645 | uh->uc.meta = uh->meta.head; 646 | uclient_http_process_headers(uh); 647 | 648 | if (auth_type == AUTH_TYPE_UNKNOWN && uh->uc.status_code == 401 && 649 | (uh->req_type == REQ_HEAD || uh->req_type == REQ_GET)) { 650 | uclient_http_connect(&uh->uc); 651 | uclient_http_send_headers(uh); 652 | uh->state = HTTP_STATE_REQUEST_DONE; 653 | return; 654 | } 655 | 656 | if (uh->uc.cb->header_done) 657 | uh->uc.cb->header_done(&uh->uc); 658 | 659 | if (uh->eof || seq != uh->uc.seq) 660 | return; 661 | 662 | if (uh->req_type == REQ_HEAD || uh->uc.status_code == 204 || 663 | uh->content_length == 0) { 664 | uh->eof = true; 665 | uclient_notify_eof(uh); 666 | } 667 | } 668 | 669 | static void uclient_parse_http_line(struct uclient_http *uh, char *data) 670 | { 671 | char *name; 672 | char *sep; 673 | 674 | if (uh->state == HTTP_STATE_REQUEST_DONE) { 675 | char *code; 676 | 677 | if (!strlen(data)) 678 | return; 679 | 680 | /* HTTP/1.1 */ 681 | strsep(&data, " "); 682 | 683 | code = strsep(&data, " "); 684 | if (!code) 685 | goto error; 686 | 687 | uh->uc.status_code = strtoul(code, &sep, 10); 688 | if (sep && *sep) 689 | goto error; 690 | 691 | uh->state = HTTP_STATE_RECV_HEADERS; 692 | return; 693 | } 694 | 695 | if (!*data) { 696 | uclient_http_headers_complete(uh); 697 | return; 698 | } 699 | 700 | sep = strchr(data, ':'); 701 | if (!sep) 702 | return; 703 | 704 | *(sep++) = 0; 705 | 706 | for (name = data; *name; name++) 707 | *name = tolower(*name); 708 | 709 | name = data; 710 | while (isspace(*sep)) 711 | sep++; 712 | 713 | blobmsg_add_string(&uh->meta, name, sep); 714 | return; 715 | 716 | error: 717 | uh->uc.status_code = 400; 718 | uh->eof = true; 719 | uclient_notify_eof(uh); 720 | } 721 | 722 | static void __uclient_notify_read(struct uclient_http *uh) 723 | { 724 | struct uclient *uc = &uh->uc; 725 | unsigned int seq = uh->seq; 726 | char *data; 727 | int len; 728 | 729 | if (uh->state < HTTP_STATE_REQUEST_DONE || uh->state == HTTP_STATE_ERROR) 730 | return; 731 | 732 | data = ustream_get_read_buf(uh->us, &len); 733 | if (!data || !len) 734 | return; 735 | 736 | if (uh->state < HTTP_STATE_RECV_DATA) { 737 | char *sep, *next; 738 | int cur_len; 739 | 740 | do { 741 | sep = strchr(data, '\n'); 742 | if (!sep) 743 | break; 744 | 745 | next = sep + 1; 746 | if (sep > data && sep[-1] == '\r') 747 | sep--; 748 | 749 | /* Check for multi-line HTTP headers */ 750 | if (sep > data) { 751 | if (!*next) 752 | return; 753 | 754 | if (isspace(*next) && *next != '\r' && *next != '\n') { 755 | sep[0] = ' '; 756 | if (sep + 1 < next) 757 | sep[1] = ' '; 758 | continue; 759 | } 760 | } 761 | 762 | *sep = 0; 763 | cur_len = next - data; 764 | uclient_parse_http_line(uh, data); 765 | if (seq != uh->seq) 766 | return; 767 | 768 | ustream_consume(uh->us, cur_len); 769 | len -= cur_len; 770 | 771 | if (uh->eof) 772 | return; 773 | 774 | data = ustream_get_read_buf(uh->us, &len); 775 | } while (data && uh->state < HTTP_STATE_RECV_DATA); 776 | 777 | if (!len) 778 | return; 779 | } 780 | 781 | if (uh->eof) 782 | return; 783 | 784 | if (uh->state == HTTP_STATE_RECV_DATA) { 785 | /* Now it's uclient user turn to read some data */ 786 | uloop_timeout_cancel(&uc->connection_timeout); 787 | uclient_backend_read_notify(uc); 788 | } 789 | } 790 | 791 | static void __uclient_notify_write(struct uclient_http *uh) 792 | { 793 | struct uclient *uc = &uh->uc; 794 | 795 | if (uc->cb->data_sent) 796 | uc->cb->data_sent(uc); 797 | } 798 | 799 | static void uclient_notify_read(struct ustream *us, int bytes) 800 | { 801 | struct uclient_http *uh = container_of(us, struct uclient_http, ufd.stream); 802 | 803 | __uclient_notify_read(uh); 804 | } 805 | 806 | static void uclient_notify_write(struct ustream *us, int bytes) 807 | { 808 | struct uclient_http *uh = container_of(us, struct uclient_http, ufd.stream); 809 | 810 | __uclient_notify_write(uh); 811 | } 812 | 813 | static void uclient_notify_state(struct ustream *us) 814 | { 815 | struct uclient_http *uh = container_of(us, struct uclient_http, ufd.stream); 816 | 817 | if (uh->ufd.stream.write_error) { 818 | uclient_http_error(uh, UCLIENT_ERROR_CONNECT); 819 | return; 820 | } 821 | uclient_notify_eof(uh); 822 | } 823 | 824 | static int uclient_setup_http(struct uclient_http *uh) 825 | { 826 | struct ustream *us = &uh->ufd.stream; 827 | int ret; 828 | 829 | memset(&uh->ufd, 0, sizeof(uh->ufd)); 830 | uh->us = us; 831 | uh->ssl = false; 832 | 833 | us->string_data = true; 834 | us->notify_state = uclient_notify_state; 835 | us->notify_read = uclient_notify_read; 836 | us->notify_write = uclient_notify_write; 837 | 838 | ret = uclient_do_connect(uh, "80"); 839 | if (ret) 840 | return UCLIENT_ERROR_CONNECT; 841 | 842 | ustream_fd_init(&uh->ufd, uh->fd); 843 | 844 | return 0; 845 | } 846 | 847 | static void uclient_ssl_notify_read(struct ustream *us, int bytes) 848 | { 849 | struct uclient_http *uh = container_of(us, struct uclient_http, ussl.stream); 850 | 851 | __uclient_notify_read(uh); 852 | } 853 | 854 | static void uclient_ssl_notify_write(struct ustream *us, int bytes) 855 | { 856 | struct uclient_http *uh = container_of(us, struct uclient_http, ussl.stream); 857 | 858 | __uclient_notify_write(uh); 859 | } 860 | 861 | static void uclient_ssl_notify_state(struct ustream *us) 862 | { 863 | struct uclient_http *uh = container_of(us, struct uclient_http, ussl.stream); 864 | 865 | uclient_notify_eof(uh); 866 | } 867 | 868 | static void uclient_ssl_notify_error(struct ustream_ssl *ssl, int error, const char *str) 869 | { 870 | struct uclient_http *uh = container_of(ssl, struct uclient_http, ussl); 871 | struct uclient *uc = &uh->uc; 872 | 873 | if (uc->cb->log_msg) 874 | uc->cb->log_msg(uc, UCLIENT_LOG_SSL_ERROR, str); 875 | uclient_http_error(uh, UCLIENT_ERROR_CONNECT); 876 | } 877 | 878 | static void uclient_ssl_notify_verify_error(struct ustream_ssl *ssl, int error, const char *str) 879 | { 880 | struct uclient_http *uh = container_of(ssl, struct uclient_http, ussl); 881 | struct uclient *uc = &uh->uc; 882 | 883 | if (!uh->ssl_require_validation) 884 | return; 885 | 886 | if (uc->cb->log_msg) 887 | uc->cb->log_msg(uc, UCLIENT_LOG_SSL_VERIFY_ERROR, str); 888 | uclient_http_error(uh, UCLIENT_ERROR_SSL_INVALID_CERT); 889 | } 890 | 891 | static void uclient_ssl_notify_connected(struct ustream_ssl *ssl) 892 | { 893 | struct uclient_http *uh = container_of(ssl, struct uclient_http, ussl); 894 | 895 | if (!uh->ssl_require_validation) 896 | return; 897 | 898 | if (!uh->ussl.valid_cn) 899 | uclient_http_error(uh, UCLIENT_ERROR_SSL_CN_MISMATCH); 900 | } 901 | 902 | static int uclient_setup_https(struct uclient_http *uh) 903 | { 904 | struct ustream *us = &uh->ussl.stream; 905 | int ret; 906 | 907 | memset(&uh->ussl, 0, sizeof(uh->ussl)); 908 | uh->ssl = true; 909 | uh->us = us; 910 | 911 | if (!uh->ssl_ctx) 912 | return UCLIENT_ERROR_MISSING_SSL_CONTEXT; 913 | 914 | ret = uclient_do_connect(uh, "443"); 915 | if (ret) 916 | return UCLIENT_ERROR_CONNECT; 917 | 918 | us->string_data = true; 919 | us->notify_state = uclient_ssl_notify_state; 920 | us->notify_read = uclient_ssl_notify_read; 921 | us->notify_write = uclient_ssl_notify_write; 922 | uh->ussl.notify_error = uclient_ssl_notify_error; 923 | uh->ussl.notify_verify_error = uclient_ssl_notify_verify_error; 924 | uh->ussl.notify_connected = uclient_ssl_notify_connected; 925 | uh->ussl.server_name = uh->uc.url->host; 926 | uh->ssl_ops->init_fd(&uh->ussl, uh->fd, uh->ssl_ctx, false); 927 | uh->ssl_ops->set_peer_cn(&uh->ussl, uh->uc.url->host); 928 | 929 | return 0; 930 | } 931 | 932 | static int uclient_http_connect(struct uclient *cl) 933 | { 934 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 935 | int ret; 936 | 937 | if (!cl->eof || uh->disconnect || uh->connection_close) 938 | uclient_http_disconnect(uh); 939 | 940 | uclient_http_init_request(uh); 941 | 942 | if (uh->us) 943 | return 0; 944 | 945 | uh->ssl = cl->url->prefix == PREFIX_HTTPS; 946 | 947 | if (uh->ssl) 948 | ret = uclient_setup_https(uh); 949 | else 950 | ret = uclient_setup_http(uh); 951 | 952 | return ret; 953 | } 954 | 955 | static void uclient_http_disconnect_cb(struct uloop_timeout *timeout) 956 | { 957 | struct uclient_http *uh = container_of(timeout, struct uclient_http, disconnect_t); 958 | 959 | uclient_http_disconnect(uh); 960 | } 961 | 962 | static struct uclient *uclient_http_alloc(void) 963 | { 964 | struct uclient_http *uh; 965 | 966 | uh = calloc_a(sizeof(*uh)); 967 | if (!uh) 968 | return NULL; 969 | 970 | uh->disconnect_t.cb = uclient_http_disconnect_cb; 971 | blob_buf_init(&uh->headers, 0); 972 | 973 | return &uh->uc; 974 | } 975 | 976 | static void uclient_http_free_ssl_ctx(struct uclient_http *uh) 977 | { 978 | uh->ssl_ops = NULL; 979 | uh->ssl_ctx = NULL; 980 | } 981 | 982 | static void uclient_http_free(struct uclient *cl) 983 | { 984 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 985 | 986 | uclient_http_free_url_state(cl); 987 | uclient_http_free_ssl_ctx(uh); 988 | blob_buf_free(&uh->headers); 989 | blob_buf_free(&uh->meta); 990 | free(uh); 991 | } 992 | 993 | int 994 | uclient_http_set_request_type(struct uclient *cl, const char *type) 995 | { 996 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 997 | unsigned int i; 998 | 999 | if (cl->backend != &uclient_backend_http) 1000 | return -1; 1001 | 1002 | if (uh->state > HTTP_STATE_INIT) 1003 | return -1; 1004 | 1005 | for (i = 0; i < ARRAY_SIZE(request_types); i++) { 1006 | if (strcmp(request_types[i], type) != 0) 1007 | continue; 1008 | 1009 | uh->req_type = i; 1010 | return 0; 1011 | } 1012 | 1013 | return -1; 1014 | } 1015 | 1016 | int 1017 | uclient_http_reset_headers(struct uclient *cl) 1018 | { 1019 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1020 | 1021 | blob_buf_init(&uh->headers, 0); 1022 | 1023 | return 0; 1024 | } 1025 | 1026 | int 1027 | uclient_http_set_header(struct uclient *cl, const char *name, const char *value) 1028 | { 1029 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1030 | 1031 | if (cl->backend != &uclient_backend_http) 1032 | return -1; 1033 | 1034 | if (uh->state > HTTP_STATE_INIT) 1035 | return -1; 1036 | 1037 | blobmsg_add_string(&uh->headers, name, value); 1038 | return 0; 1039 | } 1040 | 1041 | static int 1042 | uclient_http_send_data(struct uclient *cl, const char *buf, unsigned int len) 1043 | { 1044 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1045 | int err; 1046 | 1047 | if (uh->state >= HTTP_STATE_REQUEST_DONE) 1048 | return -1; 1049 | 1050 | err = uclient_http_send_headers(uh); 1051 | if (err) 1052 | return err; 1053 | 1054 | if (len > 0) { 1055 | ustream_printf(uh->us, "%X\r\n", len); 1056 | ustream_write(uh->us, buf, len, false); 1057 | ustream_printf(uh->us, "\r\n"); 1058 | } 1059 | 1060 | return len; 1061 | } 1062 | 1063 | static int 1064 | uclient_http_request_done(struct uclient *cl) 1065 | { 1066 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1067 | int err; 1068 | 1069 | if (uh->state >= HTTP_STATE_REQUEST_DONE) 1070 | return -1; 1071 | 1072 | err = uclient_http_send_headers(uh); 1073 | if (err) 1074 | return err; 1075 | 1076 | if (uclient_request_supports_body(uh->req_type)) 1077 | ustream_printf(uh->us, "0\r\n\r\n"); 1078 | uh->state = HTTP_STATE_REQUEST_DONE; 1079 | 1080 | return 0; 1081 | } 1082 | 1083 | static int 1084 | uclient_http_read(struct uclient *cl, char *buf, unsigned int len) 1085 | { 1086 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1087 | int read_len = 0; 1088 | char *data, *data_end; 1089 | 1090 | if (uh->state < HTTP_STATE_RECV_DATA || !uh->us) 1091 | return 0; 1092 | 1093 | data = ustream_get_read_buf(uh->us, &read_len); 1094 | if (!data || !read_len) { 1095 | ustream_poll(uh->us); 1096 | data = ustream_get_read_buf(uh->us, &read_len); 1097 | if (!data || !read_len) 1098 | return 0; 1099 | } 1100 | 1101 | data_end = data + read_len; 1102 | read_len = 0; 1103 | 1104 | if (uh->read_chunked == 0) { 1105 | char *sep; 1106 | 1107 | if (data[0] == '\r' && data[1] == '\n') { 1108 | data += 2; 1109 | read_len += 2; 1110 | } 1111 | 1112 | sep = strstr(data, "\r\n"); 1113 | if (!sep) 1114 | return 0; 1115 | 1116 | *sep = 0; 1117 | uh->read_chunked = strtoul(data, NULL, 16); 1118 | 1119 | read_len += sep + 2 - data; 1120 | data = sep + 2; 1121 | 1122 | if (!uh->read_chunked) { 1123 | uh->eof = true; 1124 | uh->uc.data_eof = true; 1125 | } 1126 | } 1127 | 1128 | unsigned int diff = data_end - data; 1129 | if (len > diff) 1130 | len = diff; 1131 | 1132 | if (uh->read_chunked >= 0) { 1133 | if (len > (unsigned long) uh->read_chunked) 1134 | len = uh->read_chunked; 1135 | 1136 | uh->read_chunked -= len; 1137 | } else if (uh->content_length >= 0) { 1138 | if (len > (unsigned long) uh->content_length) 1139 | len = uh->content_length; 1140 | 1141 | uh->content_length -= len; 1142 | if (!uh->content_length) { 1143 | uh->eof = true; 1144 | uh->uc.data_eof = true; 1145 | } 1146 | } 1147 | 1148 | if (len > 0) { 1149 | read_len += len; 1150 | memcpy(buf, data, len); 1151 | } 1152 | 1153 | if (read_len > 0) 1154 | ustream_consume(uh->us, read_len); 1155 | 1156 | uclient_notify_eof(uh); 1157 | 1158 | /* Now that we consumed something and if this isn't EOF, start timer again */ 1159 | if (!uh->uc.eof && !cl->connection_timeout.pending) 1160 | uloop_timeout_set(&cl->connection_timeout, cl->timeout_msecs); 1161 | 1162 | return len; 1163 | } 1164 | 1165 | int uclient_http_redirect(struct uclient *cl) 1166 | { 1167 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1168 | struct blobmsg_policy location = { 1169 | .name = "location", 1170 | .type = BLOBMSG_TYPE_STRING, 1171 | }; 1172 | struct uclient_url *url = cl->url; 1173 | struct blob_attr *tb; 1174 | 1175 | if (cl->backend != &uclient_backend_http) 1176 | return false; 1177 | 1178 | if (!uclient_http_status_redirect(cl)) 1179 | return false; 1180 | 1181 | blobmsg_parse(&location, 1, &tb, blob_data(uh->meta.head), blob_len(uh->meta.head)); 1182 | if (!tb) 1183 | return false; 1184 | 1185 | url = uclient_get_url_location(url, blobmsg_data(tb)); 1186 | if (!url) 1187 | return false; 1188 | 1189 | if (cl->proxy_url) { 1190 | free(cl->proxy_url); 1191 | cl->proxy_url = url; 1192 | } 1193 | else { 1194 | free(cl->url); 1195 | cl->url = url; 1196 | } 1197 | 1198 | if (uclient_http_connect(cl)) 1199 | return -1; 1200 | 1201 | uclient_http_request_done(cl); 1202 | 1203 | return true; 1204 | } 1205 | 1206 | int uclient_http_set_ssl_ctx(struct uclient *cl, const struct ustream_ssl_ops *ops, 1207 | struct ustream_ssl_ctx *ctx, bool require_validation) 1208 | { 1209 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1210 | 1211 | if (cl->backend != &uclient_backend_http) 1212 | return -1; 1213 | 1214 | uclient_http_free_url_state(cl); 1215 | 1216 | uclient_http_free_ssl_ctx(uh); 1217 | uh->ssl_ops = ops; 1218 | uh->ssl_ctx = ctx; 1219 | uh->ssl_require_validation = !!ctx && require_validation; 1220 | 1221 | return 0; 1222 | } 1223 | 1224 | int uclient_http_set_address_family(struct uclient *cl, int af) 1225 | { 1226 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1227 | 1228 | if (cl->backend != &uclient_backend_http) 1229 | return -1; 1230 | 1231 | switch (af) { 1232 | case AF_INET: 1233 | uh->usock_flags = USOCK_IPV4ONLY; 1234 | break; 1235 | case AF_INET6: 1236 | uh->usock_flags = USOCK_IPV6ONLY; 1237 | break; 1238 | default: 1239 | uh->usock_flags = 0; 1240 | break; 1241 | } 1242 | 1243 | return 0; 1244 | } 1245 | 1246 | static int 1247 | uclient_http_pending_bytes(struct uclient *cl, bool write) 1248 | { 1249 | struct uclient_http *uh = container_of(cl, struct uclient_http, uc); 1250 | 1251 | return ustream_pending_data(uh->us, write); 1252 | } 1253 | 1254 | const struct uclient_backend uclient_backend_http = { 1255 | .prefix = uclient_http_prefix, 1256 | 1257 | .alloc = uclient_http_alloc, 1258 | .free = uclient_http_free, 1259 | .connect = uclient_http_connect, 1260 | .disconnect = uclient_http_request_disconnect, 1261 | .update_url = uclient_http_free_url_state, 1262 | .update_proxy_url = uclient_http_free_url_state, 1263 | 1264 | .read = uclient_http_read, 1265 | .write = uclient_http_send_data, 1266 | .request = uclient_http_request_done, 1267 | .pending_bytes = uclient_http_pending_bytes, 1268 | }; 1269 | --------------------------------------------------------------------------------