ADDED .fossil-settings/clean-glob Index: .fossil-settings/clean-glob ================================================================== --- .fossil-settings/clean-glob +++ .fossil-settings/clean-glob @@ -0,0 +1,48 @@ +*.a +*.bundle +*.dep +*.dll +*.dylib +*.exe +*.framework +*.library +*.map +*.o +*.so +*.so.* +*/.deps +.deps +aclocal.m4 +autom4te.cache +boot.dol +buildsys.mk +config.h +config.h.in +config.log +config.status +configure +DerivedData +docs +extra.mk +generators/gen_tables +src/Info.plist +src/bridge/Info.plist +src/objfw-defs.h +src/runtime/amiga-library-functable.inc +src/runtime/inline.h +tests/EBOOT.PBP +tests/Info.plist +tests/PARAM.SFO +tests/objc_sync/objc_sync +tests/plugin/Info.plist +tests/terminal/terminal_tests +tests/tests +tests/tests.3dsx +tests/tests.arm9 +tests/tests.nds +utils/objfw-config +utils/ofarc/ofarc +utils/ofdns/ofdns +utils/ofhash/ofhash +utils/ofhttp/ofhttp +utils/ofsock/ofsock ADDED .fossil-settings/ignore-glob Index: .fossil-settings/ignore-glob ================================================================== --- .fossil-settings/ignore-glob +++ .fossil-settings/ignore-glob @@ -0,0 +1,53 @@ +*.a +*.bundle +*.dep +*.dll +*.dylib +*.exe +*.framework +*.library +*.map +*.o +*.orig +*.so +*.so.* +*/.deps +.deps +.git +aclocal.m4 +autom4te.cache +boot.dol +buildsys.mk +config.h +config.h.in +config.log +config.status +configure +DerivedData +docs +extra.mk +generators/gen_tables +src/Info.plist +src/bridge/Info.plist +src/objfw-defs.h +src/runtime/amiga-library-functable.inc +src/runtime/inline.h +tests/EBOOT.PBP +tests/Info.plist +tests/PARAM.SFO +tests/iOS.xcodeproj/*.pbxuser +tests/iOS.xcodeproj/project.xcworkspace +tests/iOS.xcodeproj/xcuserdata +tests/objc_sync/objc_sync +tests/plugin/Info.plist +tests/terminal/terminal_tests +tests/tests +tests/tests.3dsx +tests/tests.arm9 +tests/tests.nds +utils/objfw-config +utils/ofarc/ofarc +utils/ofdns/ofdns +utils/ofhash/ofhash +utils/ofhttp/ofhttp +utils/ofsock/ofsock ADDED .fossil-settings/keep-glob Index: .fossil-settings/keep-glob ================================================================== --- .fossil-settings/keep-glob +++ .fossil-settings/keep-glob @@ -0,0 +1,1 @@ +.git Index: .gitignore ================================================================== --- .gitignore +++ .gitignore @@ -1,21 +1,24 @@ *.a *.bundle *.dep *.dll *.dylib +*.exe +*.framework *.library +*.map *.o *.orig *.so *.so.* -*~ .deps +.fslckout +_FOSSIL_ aclocal.m4 autom4te.cache boot.dol -build buildsys.mk config.h config.h.in config.log config.status @@ -22,37 +25,29 @@ configure DerivedData docs extra.mk generators/gen_tables -generators/gen_tables.exe src/Info.plist -src/ObjFW.framework src/bridge/Info.plist -src/bridge/ObjFWBridge.framework src/objfw-defs.h -src/runtime/ObjFWRT.framework src/runtime/amiga-library-functable.inc src/runtime/inline.h -tests/*.map tests/EBOOT.PBP tests/Info.plist tests/PARAM.SFO tests/iOS.xcodeproj/*.pbxuser tests/iOS.xcodeproj/project.xcworkspace tests/iOS.xcodeproj/xcuserdata tests/objc_sync/objc_sync tests/plugin/Info.plist +tests/terminal/terminal_tests tests/tests tests/tests.3dsx tests/tests.arm9 -tests/tests.exe tests/tests.nds utils/objfw-config utils/ofarc/ofarc -utils/ofarc/ofarc.exe utils/ofdns/ofdns -utils/ofdns/ofdns.exe utils/ofhash/ofhash -utils/ofhash/ofhash.exe utils/ofhttp/ofhttp -utils/ofhttp/ofhttp.exe +utils/ofsock/ofsock Index: .travis.yml ================================================================== --- .travis.yml +++ .travis.yml @@ -2,32 +2,84 @@ matrix: include: # Linux - os: linux - compiler: gcc - dist: bionic - sudo: required - - os: linux - compiler: clang - dist: bionic - sudo: required - - os: linux - compiler: gcc - dist: xenial - sudo: required - - os: linux - compiler: clang - dist: xenial - sudo: required + compiler: clang + dist: precise + sudo: required + + - os: linux + compiler: gcc + dist: precise + sudo: required + + - os: linux + arch: arm64 + compiler: clang + dist: precise + sudo: required + + - os: linux + arch: arm64 + compiler: gcc + dist: precise + sudo: required + + - os: linux + arch: ppc64le + compiler: clang + dist: precise + sudo: required + + - os: linux + arch: ppc64le + compiler: gcc + dist: precise + sudo: required + + # Clang seems to have broken exceptions on s390x + #- os: linux + # arch: s390x + # compiler: clang + # dist: precise + # sudo: required + + - os: linux + arch: s390x + compiler: gcc + dist: precise + sudo: required + + - os: linux + compiler: clang + dist: trusty + sudo: required + - os: linux compiler: gcc dist: trusty sudo: required + + - os: linux + compiler: clang + dist: xenial + sudo: required + + - os: linux + compiler: gcc + dist: xenial + sudo: required + - os: linux compiler: clang - dist: trusty + dist: bionic + sudo: required + + - os: linux + compiler: gcc + dist: bionic sudo: required # macOS - os: osx osx_image: xcode11.2 @@ -189,18 +241,32 @@ env: - config=wii before_install: - if [ "$TRAVIS_OS_NAME" = "linux" -a -z "$config" ]; then - if ! sudo apt-get -qq update >/tmp/apt_log 2>&1; then + case "$TRAVIS_CPU_ARCH" in + amd64 | s390x) + pkgs="gobjc-multilib"; + ;; + *) + pkgs="gobjc"; + ;; + esac; + + pkgs="$pkgs libsctp-dev"; + + if grep precise /etc/lsb-release >/dev/null; then + pkgs="$pkgs ipx"; + fi; + + if ! sudo apt-get -qq install -y $pkgs >/tmp/apt_log 2>&1; then cat /tmp/apt_log; exit 1; fi; - if ! sudo apt-get -qq install -y gobjc-multilib >/tmp/apt_log 2>&1; - then - cat /tmp/apt_log; - exit 1; + + if grep precise /etc/lsb-release >/dev/null; then + sudo ipx_internal_net add 1234 123456; fi; fi - if [ "$config" = "nintendo_3ds" -o "$config" = "nintendo_ds" -o "$config" = "wii" ]; then @@ -224,11 +290,17 @@ wget -q https://franke.ms/download/amiga-gcc.tgz; tar -C / -xzf amiga-gcc.tgz; fi script: - - echo -e '%s/-DSTDOUT$/&_SIMPLE/\nwq' | ed -s tests/Makefile + # This needs to use ed on macOS, as it has no GNU sed, and sed on Linux, as + # some Travis hosts have no ed. + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then + echo -e '%s/-DSTDOUT$/&_SIMPLE/\nwq' | ed -s tests/Makefile; + else + sed -i 's/-DSTDOUT$/&_SIMPLE/' tests/Makefile; + fi - build() { if ! git clean -fxd >/tmp/clean_log 2>&1; then cat /tmp/clean_log; exit 1; @@ -252,26 +324,41 @@ } - if [ "$TRAVIS_OS_NAME" = "linux" -a -z "$config" ]; then build_32_64() { build OBJC="$CC" $@; - build OBJC="$CC -m32" --host=i686-pc-linux-gnu $@; + + case "$TRAVIS_CPU_ARCH" in + amd64) + build OBJC="$CC -m32" + --host=i686-pc-linux-gnu $@; + ;; + s390x) + build OBJC="$CC -m31" + --host=s390-pc-linux-gnu $@; + ;; + esac }; build_32_64; build_32_64 --enable-seluid24; - build_32_64 --disable-threads; - build_32_64 --disable-threads --disable-sockets; - build_32_64 --disable-threads --disable-files; - build_32_64 --disable-threads --disable-sockets --disable-files; - build_32_64 --disable-sockets; - build_32_64 --disable-sockets --disable-files; - build_32_64 --disable-files; - build_32_64 --disable-shared; - build_32_64 --disable-shared --enable-seluid24; build_32_64 --disable-compiler-tls; - build_32_64 --disable-compiler-tls --disable-threads; + + true The following are not CPU-dependent, so only run them on amd64; + if [ "$TRAVIS_CPU_ARCH" = "amd64" ]; then + build_32_64 --disable-threads; + build_32_64 --disable-threads --disable-sockets; + build_32_64 --disable-threads --disable-files; + build_32_64 --disable-threads --disable-sockets + --disable-files; + build_32_64 --disable-sockets; + build_32_64 --disable-sockets --disable-files; + build_32_64 --disable-files; + build_32_64 --disable-shared; + build_32_64 --disable-shared --enable-seluid24; + build_32_64 --disable-compiler-tls --disable-threads; + fi; fi - if [ "$TRAVIS_OS_NAME" = "osx" -a -z "$config" ]; then build_mac_32_64() { build $@; Index: Makefile ================================================================== --- Makefile +++ Makefile @@ -16,16 +16,16 @@ tarball: echo "Generating tarball for version ${PACKAGE_VERSION}..." rm -fr objfw-${PACKAGE_VERSION} objfw-${PACKAGE_VERSION}.tar \ objfw-${PACKAGE_VERSION}.tar.gz - mkdir objfw-${PACKAGE_VERSION} - git --work-tree=objfw-${PACKAGE_VERSION} checkout . - rm objfw-${PACKAGE_VERSION}/.gitignore \ - objfw-${PACKAGE_VERSION}/.travis.yml + fossil tarball --name objfw-${PACKAGE_VERSION} current - \ + --exclude '.fossil-settings/*,.gitignore,.travis.yml' | \ + ofarc -ttgz -xq - cp configure config.h.in objfw-${PACKAGE_VERSION}/ - ofarc -cq objfw-${PACKAGE_VERSION}.tar $$(find objfw-${PACKAGE_VERSION}) + ofarc -cq objfw-${PACKAGE_VERSION}.tar \ + $$(find objfw-${PACKAGE_VERSION} | sort) rm -fr objfw-${PACKAGE_VERSION} gzip -9 objfw-${PACKAGE_VERSION}.tar rm -f objfw-${PACKAGE_VERSION}.tar gpg -b objfw-${PACKAGE_VERSION}.tar.gz || true echo "Generating documentation..." @@ -34,10 +34,10 @@ rm -fr objfw-docs-${PACKAGE_VERSION} objfw-docs-${PACKAGE_VERSION}.tar \ objfw-docs-${PACKAGE_VERSION}.tar.gz mv docs objfw-docs-${PACKAGE_VERSION} echo "Generating docs tarball for version ${PACKAGE_VERSION}..." ofarc -cq objfw-docs-${PACKAGE_VERSION}.tar \ - $$(find objfw-docs-${PACKAGE_VERSION}) + $$(find objfw-docs-${PACKAGE_VERSION} | sort) rm -fr objfw-docs-${PACKAGE_VERSION} gzip -9 objfw-docs-${PACKAGE_VERSION}.tar rm -f objfw-docs-${PACKAGE_VERSION}.tar gpg -b objfw-docs-${PACKAGE_VERSION}.tar.gz || true Index: PLATFORMS.md ================================================================== --- PLATFORMS.md +++ PLATFORMS.md @@ -78,12 +78,12 @@ Linux ----- * Architectures: Alpha, ARMv6, ARM64, Itanium, m68k, MIPS (O32), RISC-V 64, - PowerPC, SH4, x86, x86_64 - * Compilers: Clang 3.0-7.0, GCC 4.6-8.2 + PowerPC, S390x, SH4, x86, x86_64 + * Compilers: Clang 3.0-9.0, GCC 4.6-8.2 * Runtimes: ObjFW macOS ----- Index: README.md ================================================================== --- README.md +++ README.md @@ -12,10 +12,11 @@ * [Installation](#installation) * [macOS and iOS](#macos-and-ios) * [Building as a framework](#building-as-a-framework) * [Using the macOS or iOS framework in Xcode](#using-the-macos-or-ios-framework-in-xcode) + * [Broken Xcode versions](#broken-xcode-versions) * [Windows](#windows) * [Getting MSYS2](#getting-msys2) * [Updating MSYS2](#updating-msys2) * [Installing MinGW-w64 using MSYS2](#installing-mingw-w64-using-msys2) * [Getting, building and installing ObjFW](#getting-building-and-installing-objfw) @@ -71,10 +72,33 @@ To use the macOS framework in Xcode, you need to add the `.framework`s to your project and add the following flags to `Other C Flags`: -fconstant-string-class=OFConstantString -fno-constant-cfstrings + +### Broken Xcode versions + + Some versions of Xcode shipped with a version of Clang that ignores + `-fconstant-string-class=OFConstantString`. This will manifest in an error + like this: + + OFAllocFailedException.m:94:10: error: cannot find interface declaration for + 'NSConstantString' + return @"Allocating an object failed!"; + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 1 error generated. + + Unfortunately, there is no workaround for this other than to + upgrade/downgrade Xcode or to build upstream Clang yourself. + + In particular, Xcode 11 Beta 1 to Beta 3 are known to be affected. While + Xcode 11 Beta 4 to Xcode 11.3 work, the bug was unfortunately reintroduced in + Xcode 11.4.1 and a fix is not expected before Xcode 11.6. + + You can get older versions of Xcode + [here](https://developer.apple.com/download) by clicking on "More" in the + top-right corner. Windows ------- Windows is only officially supported when following these instructions, as Index: build-aux/m4/buildsys.m4 ================================================================== --- build-aux/m4/buildsys.m4 +++ build-aux/m4/buildsys.m4 @@ -182,21 +182,21 @@ CLEAN_LIB='' ;; mingw* | cygwin*) AC_MSG_RESULT(MinGW / Cygwin) LIB_CFLAGS='' - LIB_LDFLAGS='-shared -Wl,--export-all-symbols,--out-implib,${SHARED_LIB}.a' + LIB_LDFLAGS='-shared -Wl,--export-all-symbols,--out-implib,lib${SHARED_LIB}.a' LIB_LDFLAGS_INSTALL_NAME='' - LIB_PREFIX='lib' + LIB_PREFIX='' LIB_SUFFIX='.dll' LDFLAGS_RPATH='-Wl,-rpath,${libdir}' PLUGIN_CFLAGS='' PLUGIN_LDFLAGS='-shared' PLUGIN_SUFFIX='.dll' LINK_PLUGIN='${LD} -o $$out ${PLUGIN_OBJS} ${PLUGIN_OBJS_EXTRA} ${PLUGIN_LDFLAGS} ${LDFLAGS} ${LIBS}' - INSTALL_LIB='&& ${MKDIR_P} ${DESTDIR}${bindir} && ${INSTALL} -m 755 $$i ${DESTDIR}${bindir}/$$i && ${INSTALL} -m 755 $$i.a ${DESTDIR}${libdir}/$$i.a' - UNINSTALL_LIB='&& rm -f ${DESTDIR}${bindir}/$$i ${DESTDIR}${libdir}/$$i.a' + INSTALL_LIB='&& ${MKDIR_P} ${DESTDIR}${bindir} && ${INSTALL} -m 755 $$i ${DESTDIR}${bindir}/$$i && ${INSTALL} -m 755 lib$$i.a ${DESTDIR}${libdir}/lib$$i.a' + UNINSTALL_LIB='&& rm -f ${DESTDIR}${bindir}/$$i ${DESTDIR}${libdir}/lib$$i.a' INSTALL_PLUGIN='&& ${INSTALL} -m 755 $$i ${DESTDIR}${plugindir}/$$i' UNINSTALL_PLUGIN='&& rm -f ${DESTDIR}${plugindir}/$$i' CLEAN_LIB='${SHARED_LIB}.a' ;; openbsd* | mirbsd*) Index: buildsys.mk.in ================================================================== --- buildsys.mk.in +++ buildsys.mk.in @@ -125,26 +125,26 @@ .SUFFIXES: .SUFFIXES: .amigalib.o .beam .c .cc .class .cxx .d .erl .lib.o .java .mo .m .mm .o .plugin.o .po .py .pyc .rc .S .xpm .PHONY: all subdirs subdirs-after pre-depend depend install install-extra uninstall uninstall-extra clean distclean locales copy-headers-into-framework ${SUBDIRS} ${SUBDIRS_AFTER} all: - ${MAKE} pre-all - ${MAKE} subdirs - ${MAKE} depend - ${MAKE} ${STATIC_LIB} ${STATIC_LIB_NOINST} ${STATIC_PIC_LIB} ${STATIC_PIC_LIB_NOINST} ${SHARED_LIB} ${SHARED_LIB_NOINST} ${FRAMEWORK} ${FRAMEWORK_NOINST} ${AMIGA_LIB} ${AMIGA_LIB_NOINST} ${PLUGIN} ${PLUGIN_NOINST} ${PROG} ${PROG_NOINST} ${JARFILE} locales - ${MAKE} subdirs-after - ${MAKE} post-all + ${MAKE} -s pre-all + ${MAKE} -s subdirs + ${MAKE} -s depend + ${MAKE} -s ${STATIC_LIB} ${STATIC_LIB_NOINST} ${STATIC_PIC_LIB} ${STATIC_PIC_LIB_NOINST} ${SHARED_LIB} ${SHARED_LIB_NOINST} ${FRAMEWORK} ${FRAMEWORK_NOINST} ${AMIGA_LIB} ${AMIGA_LIB_NOINST} ${PLUGIN} ${PLUGIN_NOINST} ${PROG} ${PROG_NOINST} ${JARFILE} locales + ${MAKE} -s subdirs-after + ${MAKE} -s post-all pre-all post-all: subdirs: ${SUBDIRS} subdirs-after: ${SUBDIRS_AFTER} ${SUBDIRS} ${SUBDIRS_AFTER}: for i in $@; do \ ${DIR_ENTER}; \ - ${MAKE} || exit $$?; \ + ${MAKE} -s || exit $$?; \ ${DIR_LEAVE}; \ done depend: pre-depend : >.deps @@ -189,11 +189,11 @@ fi ${FRAMEWORK} ${FRAMEWORK_NOINST}: ${EXT_DEPS} ${LIB_OBJS} ${LIB_OBJS_EXTRA} ${LINK_STATUS} out="$@"; \ - if rm -fr $$out && ${MKDIR_P} $$out && ${MAKE} COPY_HEADERS_IF_SUBDIR=${includesubdir} COPY_HEADERS_DESTINATION=$$PWD/$@/Headers copy-headers-into-framework && if test -f Info.plist; then ${INSTALL} -m 644 Info.plist $$out/Info.plist; fi && if test -f module.modulemap; then ${MKDIR_P} $$out/Modules && ${INSTALL} -m 644 module.modulemap $$out/Modules/module.modulemap; fi && ${LD} -o $$out/$${out%.framework} ${LIB_OBJS} ${LIB_OBJS_EXTRA} ${FRAMEWORK_LDFLAGS} ${FRAMEWORK_LDFLAGS_INSTALL_NAME} ${LDFLAGS} ${FRAMEWORK_LIBS} && ${CODESIGN} -fs ${CODESIGN_IDENTITY} --timestamp=none $$out; then \ + if rm -fr $$out && ${MKDIR_P} $$out && ${MAKE} -s COPY_HEADERS_IF_SUBDIR=${includesubdir} COPY_HEADERS_DESTINATION=$$PWD/$@/Headers copy-headers-into-framework && if test -f Info.plist; then ${INSTALL} -m 644 Info.plist $$out/Info.plist; fi && if test -f module.modulemap; then ${MKDIR_P} $$out/Modules && ${INSTALL} -m 644 module.modulemap $$out/Modules/module.modulemap; fi && ${LD} -o $$out/$${out%.framework} ${LIB_OBJS} ${LIB_OBJS_EXTRA} ${FRAMEWORK_LDFLAGS} ${FRAMEWORK_LDFLAGS_INSTALL_NAME} ${LDFLAGS} ${FRAMEWORK_LIBS} && ${CODESIGN} -fs ${CODESIGN_IDENTITY} --timestamp=none $$out; then \ ${LINK_OK}; \ else \ rm -fr $$out; false; \ ${LINK_FAILED}; \ fi @@ -200,11 +200,11 @@ copy-headers-into-framework: for i in "" ${SUBDIRS} ${SUBDIRS_AFTER}; do \ test x"$$i" = x"" && continue; \ cd $$i || exit 1; \ - ${MAKE} copy-headers-into-framework || exit $$?; \ + ${MAKE} -s copy-headers-into-framework || exit $$?; \ cd .. || exit 1; \ done if test x"${includesubdir}" = x"${COPY_HEADERS_IF_SUBDIR}"; then \ for i in "" ${INCLUDES}; do \ @@ -611,11 +611,11 @@ install: all install-extra for i in "" ${SUBDIRS} ${SUBDIRS_AFTER}; do \ test x"$$i" = x"" && continue; \ ${DIR_ENTER}; \ - ${MAKE} install || exit $$?; \ + ${MAKE} -s install || exit $$?; \ ${DIR_LEAVE}; \ done for i in "" ${SHARED_LIB}; do \ test x"$$i" = x"" && continue; \ @@ -724,11 +724,11 @@ uninstall: uninstall-extra for i in "" ${SUBDIRS} ${SUBDIRS_AFTER}; do \ test x"$$i" = x"" && continue; \ ${DIR_ENTER}; \ - ${MAKE} uninstall || exit $$?; \ + ${MAKE} -s uninstall || exit $$?; \ ${DIR_LEAVE}; \ done for i in "" ${SHARED_LIB}; do \ test x"$$i" = x"" && continue; \ @@ -839,11 +839,11 @@ clean: for i in "" ${SUBDIRS} ${SUBDIRS_AFTER}; do \ test x"$$i" = x"" && continue; \ ${DIR_ENTER}; \ - ${MAKE} clean || exit $$?; \ + ${MAKE} -s clean || exit $$?; \ ${DIR_LEAVE}; \ done : >.deps @@ -860,11 +860,11 @@ distclean: clean for i in "" ${SUBDIRS} ${SUBDIRS_AFTER}; do \ test x"$$i" = x"" && continue; \ ${DIR_ENTER}; \ - ${MAKE} distclean || exit $$?; \ + ${MAKE} -s distclean || exit $$?; \ ${DIR_LEAVE}; \ done for i in "" ${DISTCLEAN} .deps *~; do \ test x"$$i" = x"" && continue; \ @@ -880,11 +880,11 @@ print-hierarchy: for i in "" ${SUBDIRS} ${SUBDIRS_AFTER}; do \ test x"$$i" = x"" && continue; \ echo ${PRINT_HIERARCHY_PREFIX}$$i; \ cd $$i || exit $$?; \ - ${MAKE} PRINT_HIERARCHY_PREFIX=$$i/ print-hierarchy || exit $$?; \ + ${MAKE} -s PRINT_HIERARCHY_PREFIX=$$i/ print-hierarchy || exit $$?; \ cd .. || exit $$?; \ done print-var: printf '%s\n' '${${VAR}}' Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -37,11 +37,10 @@ enable_shared="no" enable_threads="no" enable_sockets="no" enable_files="no" - ac_cv_snprintf_useful_ret="yes" ;; m68k-*-amigaos*) AS_IF([test x"$OBJCFLAGS" = x""], [ OBJCFLAGS="-O0" ]) @@ -51,11 +50,10 @@ LDFLAGS="$LDFLAGS -noixemul" enable_files="yes" # Required for reading ENV: enable_shared="no" supports_amiga_lib="yes" - ac_cv_snprintf_useful_ret="yes" AS_IF([test x"$enable_amiga_lib" != x"no"], [ AC_SUBST(OBJFWRT_AMIGA_LIB, objfwrt68k.library) AC_SUBST(SFDC_TARGET, m68k-amigaos) AC_SUBST(SFD_FILE, amigaos3.sfd) @@ -422,12 +420,13 @@ #if defined(__GLIBC__) || defined(__MINGW32__) || \ defined(__NEWLIB__) || defined(__MORPHOS__) egrep_cpp_yes #endif ], [ - CPPFLAGS="-D_GNU_SOURCE $CPPFLAGS" AC_MSG_RESULT(yes) + CPPFLAGS="-D_GNU_SOURCE $CPPFLAGS" + gnu_source="yes" ], [ AC_MSG_RESULT(no) ]) case "$host_os" in @@ -817,53 +816,33 @@ AC_MSG_RESULT(no) ]) ;; esac -AC_CHECK_FUNCS(strerror_r) - AC_CHECK_LIB(m, fmod, LIBS="$LIBS -lm") AC_CHECK_LIB(complex, creal, TESTS_LIBS="$TESTS_LIBS -lcomplex") AC_CHECK_FUNC(asprintf, [ case "$host" in - *-psp-*) - dnl asprintf is broken on the PSP, but snprintf works. + *-*-mingw*) + dnl asprintf from MinGW is broken on older Windows + dnl versions have_asprintf="no" - ac_cv_snprintf_useful_ret="yes" + ;; + *-psp-*) + dnl asprintf is broken on the PSP + have_asprintf="no" ;; *) have_asprintf="yes" AC_DEFINE(HAVE_ASPRINTF, 1, [Whether we have asprintf()]) ;; esac ], [ have_asprintf="no" - - AC_MSG_CHECKING(whether snprintf returns something useful) - AC_CACHE_VAL(ac_cv_snprintf_useful_ret, [ - AC_TRY_RUN([ - #include - - int - main() - { - return (snprintf(NULL, 0, "asd") == 3 ? 0 : 1); - } - ], [ - ac_cv_snprintf_useful_ret="yes" - ], [ - ac_cv_snprintf_useful_ret="no" - ], [ - ac_cv_snprintf_useful_ret="no" - ]) - ]) - AC_MSG_RESULT($ac_cv_snprintf_useful_ret) -]) -test x"$have_asprintf" != x"yes" -a x"$ac_cv_snprintf_useful_ret" != x"yes" && \ - AC_MSG_ERROR(No asprintf and no snprintf returning required space!) +]) AC_ARG_ENABLE(unicode-tables, AS_HELP_STRING([--disable-unicode-tables], [Disable Unicode tables])) AS_IF([test x"$enable_unicode_tables" != x"no"], [ AC_DEFINE(OF_HAVE_UNICODE_TABLES, 1, @@ -1228,10 +1207,59 @@ AC_CHECK_HEADERS(fcntl.h dirent.h) AC_CHECK_FUNCS([sysconf gmtime_r localtime_r nanosleep fcntl]) AC_CHECK_HEADERS(xlocale.h) AC_CHECK_FUNCS([strtod_l strtof_l asprintf_l]) +AS_IF([test x"$gnu_source" != x"yes" -a \( \ + x"$ac_cv_func_strtod_l" = x"yes" -o x"$ac_cv_func_strtof_l" = x"yes" -o \ + x"$ac_cv_func_asprintf_l" = x"yes" \)], [ + AC_MSG_CHECKING(whether *_l functions need _GNU_SOURCE) + AC_TRY_COMPILE([ + #include + #include + + #include + #ifdef HAVE_XLOCALE_H + # include + #endif + ], [ + #ifdef HAVE_STRTOD_L + (void)strtod_l; + #endif + #ifdef HAVE_STRTOF_L + (void)strtof_l; + #endif + #ifdef HAVE_ASPRINTF_L + (void)asprintf_l; + #endif + ], [ + AC_MSG_RESULT(no) + ], [ + AC_MSG_RESULT(yes) + CPPFLAGS="-D_GNU_SOURCE $CPPFLAGS" + ]) +]) + +dnl This check needs to happen after the above, as _GNU_SOURCE can change the +dnl return type. +AC_CHECK_FUNCS(strerror_r, [ + AC_MSG_CHECKING(for return type of strerror_r) + AC_TRY_COMPILE([ + #include + #include + ], [ + switch (strerror_r(0, NULL, 0)) { + case 0:; + } + ], [ + AC_MSG_RESULT(int) + ], [ + AC_MSG_RESULT(char *) + AC_DEFINE(STRERROR_R_RETURNS_CHARP, 1, + [Whether strerror_r returns char *]) + ]) +]) AC_CHECK_HEADERS(sys/utsname.h) AC_CHECK_FUNCS(uname) case "$host_os" in @@ -1274,11 +1302,21 @@ ]) AC_CHECK_HEADER(netinet/tcp.h, [ AC_DEFINE(OF_HAVE_NETINET_TCP_H, 1, [Whether we have netinet/tcp.h]) ]) + AC_CHECK_HEADER(netinet/sctp.h, [ + AC_DEFINE(OF_HAVE_SCTP, 1, [Whether we have SCTP]) + AC_DEFINE(OF_HAVE_NETINET_SCTP_H, 1, + [Whether we have netinet/sctp.h]) + AC_SUBST(USE_SRCS_SCTP, '${SRCS_SCTP}') + ]) AC_CHECK_HEADERS([arpa/inet.h netdb.h]) + AC_CHECK_HEADER(netipx/ipx.h, [ + AC_DEFINE(OF_HAVE_NETIPX_IPX_H, 1, + [Whether we have netipx/ipx.h]) + ]) AC_CHECK_MEMBER([struct sockaddr_in6.sin6_addr], [ AC_EGREP_CPP(egrep_cpp_yes, [ #ifdef _WIN32 typedef int BOOL; @@ -1324,65 +1362,138 @@ # endif # include # include #endif ]) + + AC_CHECK_MEMBER(struct sockaddr_ipx.sipx_network, [], [ + AC_CHECK_MEMBER(struct sockaddr_ipx.sa_netnum, [], [], [ + #ifdef _WIN32 + typedef int BOOL; + #endif + + #ifdef OF_HAVE_NETIPX_IPX_H + # include + #endif + + #ifdef _WIN32 + # ifdef __MINGW32__ + # include <_mingw.h> + # ifdef __MINGW64_VERSION_MAJOR + # include + # endif + # endif + # include + # include + #endif + ]) + ], [ + #ifdef _WIN32 + typedef int BOOL; + #endif + + #ifdef OF_HAVE_NETIPX_IPX_H + # include + #endif + + #ifdef _WIN32 + # ifdef __MINGW32__ + # include <_mingw.h> + # ifdef __MINGW64_VERSION_MAJOR + # include + # endif + # endif + # include + # include + #endif + ]) + AS_IF([test x"$ac_cv_member_struct_sockaddr_ipx_sipx_network" = x"yes" \ + -o x"$ac_cv_member_struct_sockaddr_ipx_sa_netnum" = x"yes"], [ + AC_EGREP_CPP(egrep_cpp_yes, [ + #ifdef _WIN32 + typedef int BOOL; + #endif + + #ifdef OF_HAVE_SYS_SOCKET_H + # include + #endif + + #ifdef _WIN32 + # ifdef __MINGW32__ + # include <_mingw.h> + # ifdef __MINGW64_VERSION_MAJOR + # include + # endif + # endif + # include + # include + #endif + + #ifdef AF_IPX + egrep_cpp_yes + #endif + ], [ + AC_DEFINE(OF_HAVE_IPX, 1, [Whether we have IPX/SPX]) + AC_SUBST(USE_SRCS_IPX, '${SRCS_IPX}') + ]) + ]) AC_CHECK_FUNCS(paccept accept4, break) AC_CHECK_FUNCS(kqueue1 kqueue, [ AC_DEFINE(HAVE_KQUEUE, 1, [Whether we have kqueue]) - AC_SUBST(OFKQUEUEKERNELEVENTOBSERVER_M, + AC_SUBST(OF_KQUEUE_KERNEL_EVENT_OBSERVER_M, "OFKqueueKernelEventObserver.m") break ]) AC_CHECK_FUNCS(epoll_create1 epoll_create, [ AC_DEFINE(HAVE_EPOLL, 1, [Whether we have epoll]) - AC_SUBST(OFEPOLLKERNELEVENTOBSERVER_M, + AC_SUBST(OF_EPOLL_KERNEL_EVENT_OBSERVER_M, "OFEpollKernelEventObserver.m") break ]) AS_IF([test x"$with_wii" = x"yes"], [ AC_DEFINE(HAVE_POLL, 1, [Whether we have poll()]) - AC_SUBST(OFPOLLKERNELEVENTOBSERVER_M, + AC_SUBST(OF_POLL_KERNEL_EVENT_OBSERVER_M, "OFPollKernelEventObserver.m") ], [ AC_CHECK_HEADERS(poll.h) AC_CHECK_FUNC(poll, [ AC_DEFINE(HAVE_POLL, 1, [Whether we have poll()]) - AC_SUBST(OFPOLLKERNELEVENTOBSERVER_M, + AC_SUBST(OF_POLL_KERNEL_EVENT_OBSERVER_M, "OFPollKernelEventObserver.m") ]) ]) case "$host_os" in amigaos* | mingw* | morphos*) AC_DEFINE(HAVE_SELECT, 1, [Whether we have select() or similar]) - AC_SUBST(OFSELECTKERNELEVENTOBSERVER_M, + AC_SUBST(OF_SELECT_KERNEL_EVENT_OBSERVER_M, "OFSelectKernelEventObserver.m") ;; *) AC_CHECK_HEADERS(sys/select.h) AC_CHECK_FUNC(select, [ AC_DEFINE(HAVE_SELECT, 1, [Whether we have select() or similar]) - AC_SUBST(OFSELECTKERNELEVENTOBSERVER_M, + AC_SUBST(OF_SELECT_KERNEL_EVENT_OBSERVER_M, "OFSelectKernelEventObserver.m") ]) ;; esac AS_IF([test x"$enable_threads" != x"no"], [ - AC_SUBST(OFHTTPCLIENTTESTS_M, "OFHTTPClientTests.m") + AC_SUBST(OF_HTTP_CLIENT_TESTS_M, "OFHTTPClientTests.m") ]) AC_SUBST(OFDNS, "ofdns") AS_IF([test x"$enable_files" != x"no"], [ AC_SUBST(OFHTTP, "ofhttp") ]) + AC_SUBST(OFSOCK, "ofsock") ]) AC_DEFUN([CHECK_BUILTIN_BSWAP], [ AC_MSG_CHECKING(for __builtin_bswap$1) AC_TRY_LINK([ @@ -1439,15 +1550,16 @@ ]) ]) ;; esac AS_IF([test x"$have_processes" = x"yes"], [ - AC_SUBST(OFPROCESS_M, "OFProcess.m") + AC_SUBST(OF_PROCESS_M, "OFProcess.m") AC_DEFINE(OF_HAVE_PROCESSES, 1, [Whether we have processes]) ]) AC_CHECK_HEADERS_ONCE([complex.h sys/ioctl.h sys/ttycom.h]) +AC_CHECK_FUNCS(isatty) AC_CHECK_FUNC(pledge, [ AC_DEFINE(OF_HAVE_PLEDGE, 1, [Whether we have pledge()]) ]) @@ -1479,11 +1591,11 @@ AC_TRY_COMPILE([], [ int (^foo)(int bar); foo = ^ (int bar) { return 0; } ], [ OBJFW_OBJCFLAGS="$OBJFW_OBJCFLAGS -Xclang -fblocks" - AC_SUBST(OFBLOCKTESTS_M, "OFBlockTests.m") + AC_SUBST(OF_BLOCK_TESTS_M, "OFBlockTests.m") AC_MSG_RESULT(yes) ], [ AC_MSG_RESULT(no) OBJCFLAGS="$old_OBJCFLAGS" ]) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -52,21 +52,22 @@ LOOKUP_ASM_LIB_A = @LOOKUP_ASM_LIB_A@ LOOKUP_ASM_LOOKUP_ASM_A = @LOOKUP_ASM_LOOKUP_ASM_A@ LOOKUP_ASM_LOOKUP_ASM_LIB_A = @LOOKUP_ASM_LOOKUP_ASM_LIB_A@ MAP_LDFLAGS = @MAP_LDFLAGS@ OFARC = @OFARC@ -OFBLOCKTESTS_M = @OFBLOCKTESTS_M@ OFDNS = @OFDNS@ -OFEPOLLKERNELEVENTOBSERVER_M = @OFEPOLLKERNELEVENTOBSERVER_M@ OFHASH = @OFHASH@ OFHTTP = @OFHTTP@ -OFHTTPCLIENTTESTS_M = @OFHTTPCLIENTTESTS_M@ -OFKQUEUEKERNELEVENTOBSERVER_M = @OFKQUEUEKERNELEVENTOBSERVER_M@ -OFPOLLKERNELEVENTOBSERVER_M = @OFPOLLKERNELEVENTOBSERVER_M@ -OFPROCESS_M = @OFPROCESS_M@ -OFSELECTKERNELEVENTOBSERVER_M = @OFSELECTKERNELEVENTOBSERVER_M@ -OFSTDIOSTREAM_WIN32CONSOLE_M = @OFSTDIOSTREAM_WIN32CONSOLE_M@ +OFSOCK = @OFSOCK@ +OF_BLOCK_TESTS_M = @OF_BLOCK_TESTS_M@ +OF_EPOLL_KERNEL_EVENT_OBSERVER_M = @OF_EPOLL_KERNEL_EVENT_OBSERVER_M@ +OF_HTTP_CLIENT_TESTS_M = @OF_HTTP_CLIENT_TESTS_M@ +OF_KQUEUE_KERNEL_EVENT_OBSERVER_M = @OF_KQUEUE_KERNEL_EVENT_OBSERVER_M@ +OF_POLL_KERNEL_EVENT_OBSERVER_M = @OF_POLL_KERNEL_EVENT_OBSERVER_M@ +OF_PROCESS_M = @OF_PROCESS_M@ +OF_SCTP_SOCKET_M = @OF_SCTP_SOCKET_M@ +OF_SELECT_KERNEL_EVENT_OBSERVER_M = @OF_SELECT_KERNEL_EVENT_OBSERVER_M@ REEXPORT_RUNTIME = @REEXPORT_RUNTIME@ REEXPORT_RUNTIME_FRAMEWORK = @REEXPORT_RUNTIME_FRAMEWORK@ RUNTIME = @RUNTIME@ RUNTIME_FRAMEWORK_LIBS = @RUNTIME_FRAMEWORK_LIBS@ RUNTIME_LIBS = @RUNTIME_LIBS@ @@ -79,10 +80,12 @@ TESTS_LIBS = @TESTS_LIBS@ TESTS_STATIC_LIB = @TESTS_STATIC_LIB@ UNICODE_M = @UNICODE_M@ USE_INCLUDES_ATOMIC = @USE_INCLUDES_ATOMIC@ USE_SRCS_FILES = @USE_SRCS_FILES@ +USE_SRCS_IPX = @USE_SRCS_IPX@ USE_SRCS_PLUGINS = @USE_SRCS_PLUGINS@ +USE_SRCS_SCTP = @USE_SRCS_SCTP@ USE_SRCS_SOCKETS = @USE_SRCS_SOCKETS@ USE_SRCS_THREADS = @USE_SRCS_THREADS@ USE_SRCS_WINDOWS = @USE_SRCS_WINDOWS@ WRAPPER = @WRAPPER@ Index: generators/Makefile ================================================================== --- generators/Makefile +++ generators/Makefile @@ -5,25 +5,25 @@ .PHONY: run run: all rm -f libobjfw.so.${OBJFW_LIB_MAJOR} rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} - rm -f libobjfw.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib + rm -f objfw.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR} rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} - rm -f libobjfwrt.dll libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib + rm -f objfwrt.dll libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib rm -f ${OBJFWRT_AMIGA_LIB} if test -f ../src/libobjfw.so; then \ ${LN_S} ../src/libobjfw.so libobjfw.so.${OBJFW_LIB_MAJOR}; \ ${LN_S} ../src/libobjfw.so \ libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ elif test -f ../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; then \ ${LN_S} ../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} \ libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ fi - if test -f ../src/libobjfw.dll; then \ - ${LN_S} ../src/libobjfw.dll libobjfw.dll; \ + if test -f ../src/objfw.dll; then \ + ${LN_S} ../src/objfw.dll objfw.dll; \ fi if test -f ../src/libobjfw.dylib; then \ ${LN_S} ../src/libobjfw.dylib \ libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ fi @@ -33,12 +33,12 @@ ${LN_S} ../src/runtime/libobjfwrt.so \ libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ elif test -f ../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; then \ ${LN_S} ../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ fi - if test -f ../src/runtime/libobjfwrt.dll; then \ - ${LN_S} ../src/runtime/libobjfwrt.dll libobjfwrt.dll; \ + if test -f ../src/runtime/objfwrt.dll; then \ + ${LN_S} ../src/runtime/objfwrt.dll objfwrt.dll; \ fi if test -f ../src/runtime/libobjfwrt.dylib; then \ ${LN_S} ../src/runtime/libobjfwrt.dylib \ libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ fi @@ -51,17 +51,17 @@ DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ LIBRARY_PATH=.$${LIBRARY_PATH+:}$$LIBRARY_PATH \ ASAN_OPTIONS=allocator_may_return_null=1 \ ${WRAPPER} ./${PROG_NOINST}; EXIT=$$?; \ rm -f libobjfw.so.${OBJFW_LIB_MAJOR}; \ - rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} libobjfw.dll; \ + rm -f objfw.so.${OBJFW_LIB_MAJOR_MINOR} objfw.dll; \ rm -f libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR}; \ - rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} libobjfwrt.dll; \ + rm -f objfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} objfwrt.dll; \ rm -f libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ exit $$EXIT include ../buildsys.mk CPPFLAGS += -I../src -I../src/exceptions -I../src/runtime -I.. LIBS := -L../src -lobjfw -L../src/runtime ${RUNTIME_LIBS} ${LIBS} LD = ${OBJC} Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -53,11 +53,11 @@ OFObject.m \ OFObject+KeyValueCoding.m \ OFObject+Serialization.m \ OFOptionsParser.m \ OFPair.m \ - ${OFPROCESS_M} \ + ${OF_PROCESS_M} \ OFRIPEMD160Hash.m \ OFRunLoop.m \ OFSandbox.m \ OFSecureData.m \ OFSeekableStream.m \ @@ -119,25 +119,33 @@ SRCS_FILES = OFFile.m \ OFINICategory.m \ OFINIFile.m \ OFSettings.m \ OFString+PathAdditions.m +SRCS_IPX = OFIPXSocket.m \ + OFSPXSocket.m \ + OFSPXStreamSocket.m SRCS_PLUGINS = OFPlugin.m +SRCS_SCTP = OFSCTPSocket.m SRCS_SOCKETS = OFDNSQuery.m \ OFDNSResolver.m \ OFDNSResourceRecord.m \ OFDNSResponse.m \ + OFDatagramSocket.m \ OFHTTPClient.m \ OFHTTPCookie.m \ OFHTTPCookieManager.m \ OFHTTPRequest.m \ OFHTTPResponse.m \ OFHTTPServer.m \ + OFSequencedPacketSocket.m \ OFStreamSocket.m \ OFTCPSocket.m \ OFUDPSocket.m \ - socket.m + socket.m \ + ${USE_SRCS_IPX} \ + ${USE_SRCS_SCTP} SRCS_THREADS = OFCondition.m \ OFMutex.m \ OFRecursiveMutex.m \ condition.m \ mutex.m \ @@ -196,15 +204,17 @@ SRCS_FILES += OFFileURLHandler.m \ OFINIFileSettings.m SRCS_SOCKETS += OFDNSResolverSettings.m \ OFHTTPURLHandler.m \ OFHostAddressResolver.m \ + OFIPSocketAsyncConnector.m \ OFKernelEventObserver.m \ - ${OFEPOLLKERNELEVENTOBSERVER_M} \ - ${OFKQUEUEKERNELEVENTOBSERVER_M} \ - ${OFPOLLKERNELEVENTOBSERVER_M} \ - ${OFSELECTKERNELEVENTOBSERVER_M} + ${OF_EPOLL_KERNEL_EVENT_OBSERVER_M} \ + ${OF_KQUEUE_KERNEL_EVENT_OBSERVER_M} \ + ${OF_POLL_KERNEL_EVENT_OBSERVER_M} \ + ${OF_SELECT_KERNEL_EVENT_OBSERVER_M} \ + OFTCPSocketSOCKS5Connector.m OBJS_EXTRA = ${RUNTIME_RUNTIME_A} \ ${EXCEPTIONS_EXCEPTIONS_A} \ ${ENCODINGS_ENCODINGS_A} \ ${FORWARDING_FORWARDING_A} Index: src/OFApplication.h ================================================================== --- src/OFApplication.h +++ src/OFApplication.h @@ -18,18 +18,44 @@ #include #import "OFObject.h" OF_ASSUME_NONNULL_BEGIN + +/*! @file */ @class OFArray OF_GENERIC(ObjectType); @class OFDictionary OF_GENERIC(KeyType, ObjectType); @class OFMutableArray OF_GENERIC(ObjectType); @class OFMutableDictionary OF_GENERIC(KeyType, ObjectType); @class OFSandbox; @class OFString; +/*! + * @brief Specify the class to be used as the application delegate. + * + * An instance of this class will be created and act as the application + * delegate. + * + * For example, it can be used like this: + * + * @code + * // In MyAppDelegate.h: + * @interface MyAppDelegate: OFObject + * @end + * + * // In MyAppDelegate.m: + * OF_APPLICATION_DELEGATE(MyAppDelegate) + * + * @implementation MyAppDelegate + * - (void)applicationDidFinishLaunching + * { + * [OFApplication terminate]; + * } + * @end + * @endcode + */ #define OF_APPLICATION_DELEGATE(class_) \ int \ main(int argc, char *argv[]) \ { \ return of_application_main(&argc, &argv, \ Index: src/OFApplication.m ================================================================== --- src/OFApplication.m +++ src/OFApplication.m @@ -37,10 +37,11 @@ #import "OFPair.h" #import "OFRunLoop+Private.h" #import "OFRunLoop.h" #import "OFSandbox.h" #import "OFString.h" +#import "OFSystemInfo.h" #import "OFThread+Private.h" #import "OFThread.h" #import "OFInvalidArgumentException.h" #import "OFOutOfMemoryException.h" @@ -110,18 +111,19 @@ [[OFLocale alloc] init]; app = [[OFApplication alloc] of_init]; - [app of_setArgumentCount: argc - andArgumentValues: argv]; - #ifdef OF_WINDOWS - __wgetmainargs(&wargc, &wargv, &wenvp, _CRT_glob, &si); - [app of_setArgumentCount: wargc - andWideArgumentValues: wargv]; + if ([OFSystemInfo isWindowsNT]) { + __wgetmainargs(&wargc, &wargv, &wenvp, _CRT_glob, &si); + [app of_setArgumentCount: wargc + andWideArgumentValues: wargv]; + } else #endif + [app of_setArgumentCount: argc + andArgumentValues: argv]; app.delegate = delegate; [app of_run]; @@ -222,51 +224,101 @@ _environment = [[OFMutableDictionary alloc] init]; atexit(atexitHandler); #if defined(OF_WINDOWS) - of_char16_t *env, *env0; - env = env0 = GetEnvironmentStringsW(); - - while (*env != 0) { - void *pool = objc_autoreleasePoolPush(); - OFString *tmp, *key, *value; - size_t length, pos; - - length = of_string_utf16_length(env); - tmp = [OFString stringWithUTF16String: env - length: length]; - env += length + 1; - - /* - * cmd.exe seems to add some special variables which - * start with a "=", even though variable names are not - * allowed to contain a "=". - */ - if ([tmp hasPrefix: @"="]) { - objc_autoreleasePoolPop(pool); - continue; - } - - pos = [tmp rangeOfString: @"="].location; - if (pos == OF_NOT_FOUND) { - fprintf(stderr, "Warning: Invalid environment " - "variable: %s\n", [tmp UTF8String]); - continue; - } - - key = [tmp substringWithRange: of_range(0, pos)]; - value = [tmp substringWithRange: - of_range(pos + 1, tmp.length - pos - 1)]; - - [_environment setObject: value - forKey: key]; - - objc_autoreleasePoolPop(pool); - } - - FreeEnvironmentStringsW(env0); + if ([OFSystemInfo isWindowsNT]) { + of_char16_t *env, *env0; + env = env0 = GetEnvironmentStringsW(); + + while (*env != 0) { + void *pool = objc_autoreleasePoolPush(); + OFString *tmp, *key, *value; + size_t length, pos; + + length = of_string_utf16_length(env); + tmp = [OFString stringWithUTF16String: env + length: length]; + env += length + 1; + + /* + * cmd.exe seems to add some special variables + * which start with a "=", even though variable + * names are not allowed to contain a "=". + */ + if ([tmp hasPrefix: @"="]) { + objc_autoreleasePoolPop(pool); + continue; + } + + pos = [tmp rangeOfString: @"="].location; + if (pos == OF_NOT_FOUND) { + fprintf(stderr, + "Warning: Invalid environment " + "variable: %s\n", tmp.UTF8String); + continue; + } + + key = [tmp substringWithRange: + of_range(0, pos)]; + value = [tmp substringWithRange: + of_range(pos + 1, tmp.length - pos - 1)]; + + [_environment setObject: value + forKey: key]; + + objc_autoreleasePoolPop(pool); + } + + FreeEnvironmentStringsW(env0); + } else { + char *env, *env0; + env = env0 = GetEnvironmentStringsA(); + + while (*env != 0) { + void *pool = objc_autoreleasePoolPush(); + OFString *tmp, *key, *value; + size_t length, pos; + + length = strlen(env); + tmp = [OFString + stringWithCString: env + encoding: [OFLocale encoding] + length: length]; + env += length + 1; + + /* + * cmd.exe seems to add some special variables + * which start with a "=", even though variable + * names are not allowed to contain a "=". + */ + if ([tmp hasPrefix: @"="]) { + objc_autoreleasePoolPop(pool); + continue; + } + + pos = [tmp rangeOfString: @"="].location; + if (pos == OF_NOT_FOUND) { + fprintf(stderr, + "Warning: Invalid environment " + "variable: %s\n", tmp.UTF8String); + continue; + } + + key = [tmp substringWithRange: + of_range(0, pos)]; + value = [tmp substringWithRange: + of_range(pos + 1, tmp.length - pos - 1)]; + + [_environment setObject: value + forKey: key]; + + objc_autoreleasePoolPop(pool); + } + + FreeEnvironmentStringsA(env0); + } #elif defined(OF_AMIGAOS) void *pool = objc_autoreleasePoolPush(); OFFileManager *fileManager = [OFFileManager defaultManager]; OFArray *envContents = [fileManager contentsOfDirectoryAtPath: @"ENV:"]; @@ -436,26 +488,24 @@ } - (void)of_setArgumentCount: (int *)argc andArgumentValues: (char ***)argv { -#ifndef OF_WINDOWS void *pool = objc_autoreleasePoolPush(); OFMutableArray *arguments; of_string_encoding_t encoding; _argc = argc; _argv = argv; encoding = [OFLocale encoding]; -# ifndef OF_NINTENDO_DS +#ifndef OF_NINTENDO_DS if (*argc > 0) { -# else - if (__system_argv->argvMagic == ARGV_MAGIC && - __system_argv->argc > 0) { -# endif +#else + if (__system_argv->argvMagic == ARGV_MAGIC && __system_argv->argc > 0) { +#endif _programName = [[OFString alloc] initWithCString: (*argv)[0] encoding: encoding]; arguments = [[OFMutableArray alloc] init]; _arguments = arguments; @@ -466,14 +516,10 @@ [arguments makeImmutable]; } objc_autoreleasePoolPop(pool); -#else - _argc = argc; - _argv = argv; -#endif } #ifdef OF_WINDOWS - (void)of_setArgumentCount: (int)argc andWideArgumentValues: (wchar_t **)argv Index: src/OFArray.h ================================================================== --- src/OFArray.h +++ src/OFArray.h @@ -386,11 +386,11 @@ /*! * @brief Returns a copy of the array sorted using the specified selector and * options. * * @param selector The selector to use to sort the array. It's signature - * should be the same as that of @ref OFComparing#compare:. + * should be the same as that of -[compare:]. * @param options The options to use when sorting the array.@n * Possible values are: * Value | Description * ---------------------------|------------------------- * `OF_ARRAY_SORT_DESCENDING` | Sort in descending order Index: src/OFCharacterSet.m ================================================================== --- src/OFCharacterSet.m +++ src/OFCharacterSet.m @@ -19,23 +19,30 @@ #import "OFCharacterSet.h" #import "OFBitSetCharacterSet.h" #import "OFInvertedCharacterSet.h" #import "OFRangeCharacterSet.h" + +#import "once.h" + +@interface OFPlaceholderCharacterSet: OFCharacterSet +@end + +@interface OFWhitespaceCharacterSet: OFCharacterSet +@end static struct { Class isa; } placeholder; static OFCharacterSet *whitespaceCharacterSet = nil; -@interface OFPlaceholderCharacterSet: OFCharacterSet -@end - -@interface OFWhitespaceCharacterSet: OFCharacterSet -- (instancetype)of_init; -@end +static void +initWhitespaceCharacterSet(void) +{ + whitespaceCharacterSet = [[OFWhitespaceCharacterSet alloc] init]; +} @implementation OFPlaceholderCharacterSet - (instancetype)init { return (id)[[OFBitSetCharacterSet alloc] init]; @@ -100,11 +107,14 @@ return [[[self alloc] initWithRange: range] autorelease]; } + (OFCharacterSet *)whitespaceCharacterSet { - return [OFWhitespaceCharacterSet whitespaceCharacterSet]; + static of_once_t onceControl = OF_ONCE_INIT; + of_once(&onceControl, initWhitespaceCharacterSet); + + return whitespaceCharacterSet; } - (instancetype)init { if ([self isMemberOfClass: [OFCharacterSet class]]) { @@ -137,38 +147,15 @@ } - (OFCharacterSet *)invertedSet { return [[[OFInvertedCharacterSet alloc] - of_initWithCharacterSet: self] autorelease]; + initWithCharacterSet: self] autorelease]; } @end @implementation OFWhitespaceCharacterSet -+ (void)initialize -{ - if (self != [OFWhitespaceCharacterSet class]) - return; - - whitespaceCharacterSet = [[OFWhitespaceCharacterSet alloc] of_init]; -} - -+ (OFCharacterSet *)whitespaceCharacterSet -{ - return whitespaceCharacterSet; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)of_init -{ - return [super init]; -} - - (instancetype)autorelease { return self; } Index: src/OFColor.h ================================================================== --- src/OFColor.h +++ src/OFColor.h @@ -28,10 +28,29 @@ { float _red, _green, _blue, _alpha; OF_RESERVE_IVARS(4) } +#ifdef OF_HAVE_CLASS_PROPERTIES +@property (class, readonly, nonatomic) OFColor *black; +@property (class, readonly, nonatomic) OFColor *silver; +@property (class, readonly, nonatomic) OFColor *grey; +@property (class, readonly, nonatomic) OFColor *white; +@property (class, readonly, nonatomic) OFColor *maroon; +@property (class, readonly, nonatomic) OFColor *red; +@property (class, readonly, nonatomic) OFColor *purple; +@property (class, readonly, nonatomic) OFColor *fuchsia; +@property (class, readonly, nonatomic) OFColor *green; +@property (class, readonly, nonatomic) OFColor *lime; +@property (class, readonly, nonatomic) OFColor *olive; +@property (class, readonly, nonatomic) OFColor *yellow; +@property (class, readonly, nonatomic) OFColor *navy; +@property (class, readonly, nonatomic) OFColor *blue; +@property (class, readonly, nonatomic) OFColor *teal; +@property (class, readonly, nonatomic) OFColor *aqua; +#endif + /*! * @brief Creates a new color with the specified red, green, blue and alpha * value. * * @param red The red value of the color, between 0.0 and 1.0 @@ -43,10 +62,154 @@ + (instancetype)colorWithRed: (float)red green: (float)green blue: (float)blue alpha: (float)alpha; +/*! + * @brief Returns the HTML color `black`. + * + * The RGBA value is (0, 0, 0, 1). + * + * @return The HTML color `black` + */ ++ (OFColor *)black; + +/*! + * @brief Returns the HTML color `silver`. + * + * The RGBA value is (0.75, 0.75, 0.75, 1). + * + * @return The HTML color `silver` + */ ++ (OFColor *)silver; + +/*! + * @brief Returns the HTML color `grey`. + * + * The RGBA value is (0.5, 0.5, 0.5, 1). + * + * @return The HTML color `grey` + */ ++ (OFColor *)grey; + +/*! + * @brief Returns the HTML color `white`. + * + * The RGBA value is (1, 1, 1, 1). + * + * @return The HTML color `white` + */ ++ (OFColor *)white; + +/*! + * @brief Returns the HTML color `maroon`. + * + * The RGBA value is (0.5, 0, 0, 1). + * + * @return The HTML color `maroon` + */ ++ (OFColor *)maroon; + +/*! + * @brief Returns the HTML color `red`. + * + * The RGBA value is (1, 0, 0, 1). + * + * @return The HTML color `red` + */ ++ (OFColor *)red; + +/*! + * @brief Returns the HTML color `purple`. + * + * The RGBA value is (0.5, 0, 0.5, 1). + * + * @return The HTML color `purple` + */ ++ (OFColor *)purple; + +/*! + * @brief Returns the HTML color `fuchsia`. + * + * The RGBA value is (1, 0, 1, 1). + * + * @return The HTML color `fuchsia` + */ ++ (OFColor *)fuchsia; + +/*! + * @brief Returns the HTML color `green`. + * + * The RGBA value is (0, 0.5, 0, 1). + * + * @return The HTML color `green` + */ ++ (OFColor *)green; + +/*! + * @brief Returns the HTML color `lime`. + * + * The RGBA value is (0, 1, 0, 1). + * + * @return The HTML color `lime` + */ ++ (OFColor *)lime; + +/*! + * @brief Returns the HTML color `olive`. + * + * The RGBA value is (0.5, 0.5, 0, 1). + * + * @return The HTML color `olive` + */ ++ (OFColor *)olive; + +/*! + * @brief Returns the HTML color `yellow`. + * + * The RGBA value is (1, 1, 0, 1). + * + * @return The HTML color `yellow` + */ ++ (OFColor *)yellow; + +/*! + * @brief Returns the HTML color `navy`. + * + * The RGBA value is (0, 0, 0.5, 1). + * + * @return The HTML color `navy` + */ ++ (OFColor *)navy; + +/*! + * @brief Returns the HTML color `blue`. + * + * The RGBA value is (0, 0, 1, 1). + * + * @return The HTML color `blue` + */ ++ (OFColor *)blue; + +/*! + * @brief Returns the HTML color `teal`. + * + * The RGBA value is (0, 0.5, 0.5, 1). + * + * @return The HTML color `teal` + */ ++ (OFColor *)teal; + +/*! + * @brief Returns the HTML color `aqua`. + * + * The RGBA value is (0, 1, 1, 1). + * + * @return The HTML color `aqua` + */ ++ (OFColor *)aqua; + /*! * @brief Initializes an already allocated color with the specified red, green, * blue and alpha value. * * @param red The red value of the color, between 0.0 and 1.0 Index: src/OFColor.m ================================================================== --- src/OFColor.m +++ src/OFColor.m @@ -16,14 +16,53 @@ */ #include "config.h" #import "OFColor.h" + +#import "once.h" #import "OFInvalidArgumentException.h" @implementation OFColor +#define PREDEFINED_COLOR(name, r, g, b) \ + static OFColor *name##Color = nil; \ + \ + static void \ + initPredefinedColor_##name(void) \ + { \ + name##Color = [[OFColor alloc] initWithRed: r \ + green: g \ + blue: b \ + alpha: 1]; \ + } \ + \ + + (OFColor *)name \ + { \ + static of_once_t onceControl = OF_ONCE_INIT; \ + of_once(&onceControl, initPredefinedColor_##name); \ + \ + return name##Color; \ + } + +PREDEFINED_COLOR(black, 0.00, 0.00, 0.00) +PREDEFINED_COLOR(silver, 0.75, 0.75, 0.75) +PREDEFINED_COLOR(grey, 0.50, 0.50, 0.50) +PREDEFINED_COLOR(white, 1.00, 1.00, 1.00) +PREDEFINED_COLOR(maroon, 0.50, 0.00, 0.00) +PREDEFINED_COLOR(red, 1.00, 0.00, 0.00) +PREDEFINED_COLOR(purple, 0.50, 0.00, 0.50) +PREDEFINED_COLOR(fuchsia, 1.00, 0.00, 1.00) +PREDEFINED_COLOR(green, 0.00, 0.50, 0.00) +PREDEFINED_COLOR(lime, 0.00, 1.00, 0.00) +PREDEFINED_COLOR(olive, 0.50, 0.50, 0.00) +PREDEFINED_COLOR(yellow, 1.00, 1.00, 0.00) +PREDEFINED_COLOR(navy, 0.00, 0.00, 0.50) +PREDEFINED_COLOR(blue, 0.00, 0.00, 1.00) +PREDEFINED_COLOR(teal, 0.00, 0.50, 0.50) +PREDEFINED_COLOR(aqua, 0.00, 1.00, 1.00) + + (instancetype)colorWithRed: (float)red green: (float)green blue: (float)blue alpha: (float)alpha { @@ -84,32 +123,29 @@ } - (uint32_t)hash { uint32_t hash; - union { - float f; - unsigned char b[sizeof(float)]; - } f; + float tmp; OF_HASH_INIT(hash); - f.f = OF_BSWAP_FLOAT_IF_LE(_red); - for (uint_fast8_t i = 0; i < sizeof(float); i++) - OF_HASH_ADD(hash, f.b[i]); - - f.f = OF_BSWAP_FLOAT_IF_LE(_green); - for (uint_fast8_t i = 0; i < sizeof(float); i++) - OF_HASH_ADD(hash, f.b[i]); - - f.f = OF_BSWAP_FLOAT_IF_LE(_blue); - for (uint_fast8_t i = 0; i < sizeof(float); i++) - OF_HASH_ADD(hash, f.b[i]); - - f.f = OF_BSWAP_FLOAT_IF_LE(_alpha); - for (uint_fast8_t i = 0; i < sizeof(float); i++) - OF_HASH_ADD(hash, f.b[i]); + tmp = OF_BSWAP_FLOAT_IF_LE(_red); + for (uint_fast8_t i = 0; i < sizeof(float); i++) + OF_HASH_ADD(hash, ((char *)&tmp)[i]); + + tmp = OF_BSWAP_FLOAT_IF_LE(_green); + for (uint_fast8_t i = 0; i < sizeof(float); i++) + OF_HASH_ADD(hash, ((char *)&tmp)[i]); + + tmp = OF_BSWAP_FLOAT_IF_LE(_blue); + for (uint_fast8_t i = 0; i < sizeof(float); i++) + OF_HASH_ADD(hash, ((char *)&tmp)[i]); + + tmp = OF_BSWAP_FLOAT_IF_LE(_alpha); + for (uint_fast8_t i = 0; i < sizeof(float); i++) + OF_HASH_ADD(hash, ((char *)&tmp)[i]); OF_HASH_FINALIZE(hash); return hash; } Index: src/OFConstantString.h ================================================================== --- src/OFConstantString.h +++ src/OFConstantString.h @@ -13,11 +13,14 @@ * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ -#import "OFString.h" +#ifndef OBJFW_OF_CONSTANT_STRING_H +#define OBJFW_OF_CONSTANT_STRING_H + +#include "OFString.h" OF_ASSUME_NONNULL_BEGIN #if !defined(OF_CONSTANT_STRING_M) && \ defined(OF_APPLE_RUNTIME) && !defined(__OBJC2__) @@ -28,10 +31,11 @@ # ifdef __cplusplus } # endif #endif +#ifdef __OBJC__ /*! * @class OFConstantString OFConstantString.h ObjFW/OFConstantString.h * * @brief A class for storing constant strings using the `@""` literal. */ @@ -40,7 +44,10 @@ { char *_cString; unsigned int _cStringLength; } @end +#endif OF_ASSUME_NONNULL_END + +#endif Index: src/OFConstantString.m ================================================================== --- src/OFConstantString.m +++ src/OFConstantString.m @@ -672,10 +672,19 @@ [self finishInitialization]; return self.decomposedStringWithCompatibilityMapping; } #endif + +#ifdef OF_WINDOWS +- (OFString *)stringByExpandingWindowsEnvironmentStrings +{ + [self finishInitialization]; + + return self.stringByExpandingWindowsEnvironmentStrings; +} +#endif #ifdef OF_HAVE_FILES - (void)writeToFile: (OFString *)path { [self finishInitialization]; Index: src/OFDNSResolver.h ================================================================== --- src/OFDNSResolver.h +++ src/OFDNSResolver.h @@ -32,10 +32,11 @@ @class OFDNSResolverSettings; @class OFDate; @class OFDictionary OF_GENERIC(KeyType, ObjectType); @class OFMutableDictionary OF_GENERIC(KeyType, ObjectType); @class OFNumber; +@class OFTCPSocket; @class OFUDPSocket; /*! * @enum of_dns_resolver_error_t OFDNSResolver.h ObjFW/OFDNSResolver.h * @@ -129,10 +130,12 @@ OFUDPSocket *_IPv6Socket; #endif char _buffer[OF_DNS_RESOLVER_BUFFER_LENGTH]; OFMutableDictionary OF_GENERIC(OFNumber *, OFDNSResolverContext *) *_queries; + OFMutableDictionary OF_GENERIC(OFTCPSocket *, OFDNSResolverContext *) + *_TCPQueries; } /*! * @brief A dictionary of static hosts. * Index: src/OFDNSResolver.m ================================================================== --- src/OFDNSResolver.m +++ src/OFDNSResolver.m @@ -29,10 +29,11 @@ #import "OFDictionary.h" #import "OFHostAddressResolver.h" #import "OFNumber.h" #import "OFPair.h" #import "OFString.h" +#import "OFTCPSocket.h" #import "OFTimer.h" #import "OFUDPSocket.h" #import "OFUDPSocket+Private.h" #import "OFDNSQueryFailedException.h" @@ -46,10 +47,11 @@ #ifndef SOCK_DNS # define SOCK_DNS 0 #endif #define BUFFER_LENGTH OF_DNS_RESOLVER_BUFFER_LENGTH +#define MAX_DNS_RESPONSE_LENGTH 65536 /* * RFC 1035 doesn't specify if pointers to pointers are allowed, and if so how * many. Since it's unspecified, we have to assume that it might happen, but we * also want to limit it to avoid DoS. Limiting it to 16 levels of pointers and @@ -57,17 +59,11 @@ */ #define MAX_ALLOWED_POINTERS 16 #define CNAME_RECURSION 3 -/* - * TODO: - * - * - Fallback to TCP - */ - -@interface OFDNSResolver () +@interface OFDNSResolver () - (void)of_contextTimedOut: (OFDNSResolverContext *)context; @end @interface OFDNSResolverContext: OFObject { @@ -78,10 +74,14 @@ size_t _nameServersIndex; unsigned int _attempt; id _delegate; OFData *_queryData; of_socket_address_t _usedNameServer; + OFTCPSocket *_TCPSocket; + OFMutableData *_TCPQueryData; + void *_TCPBuffer; + size_t _responseLength; OFTimer *_cancelTimer; } - (instancetype)initWithQuery: (OFDNSQuery *)query ID: (OFNumber *)ID @@ -311,17 +311,34 @@ DNSClass: DNSClass preference: preference mailExchange: mailExchange TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RECORD_TYPE_TXT) { - OFData *textData = [OFData dataWithItems: &buffer[i] - count: dataLength]; + OFMutableArray *textStrings = [OFMutableArray array]; + + while (dataLength > 0) { + uint_fast8_t stringLength = buffer[i++]; + dataLength--; + + if (stringLength > dataLength) + @throw [OFInvalidServerReplyException + exception]; + + [textStrings addObject: + [OFData dataWithItems: buffer + i + count: stringLength]]; + + i += stringLength; + dataLength -= stringLength; + } + + [textStrings makeImmutable]; return [[[OFTXTDNSResourceRecord alloc] initWithName: name DNSClass: DNSClass - textData: textData + textStrings: textStrings TTL: TTL] autorelease]; } else if (recordType == OF_DNS_RECORD_TYPE_RP) { size_t j = i; OFString *mailbox = parseName(buffer, length, &j, MAX_ALLOWED_POINTERS); @@ -537,10 +554,12 @@ [_query release]; [_ID release]; [_settings release]; [_delegate release]; [_queryData release]; + [_TCPSocket release]; + [_TCPQueryData release]; [_cancelTimer release]; [super dealloc]; } @end @@ -568,10 +587,11 @@ self = [super init]; @try { _settings = [[OFDNSResolverSettings alloc] init]; _queries = [[OFMutableDictionary alloc] init]; + _TCPQueries = [[OFMutableDictionary alloc] init]; [_settings reload]; } @catch (id e) { [self release]; @throw e; @@ -590,10 +610,11 @@ #ifdef OF_HAVE_IPV6 [_IPv6Socket cancelAsyncRequests]; [_IPv6Socket release]; #endif [_queries release]; + [_TCPQueries release]; [super dealloc]; } - (OFDictionary *)staticHosts @@ -692,10 +713,13 @@ - (void)of_sendQueryForContext: (OFDNSResolverContext *)context runLoopMode: (of_run_loop_mode_t)runLoopMode { OFUDPSocket *sock; OFString *nameServer; + + [_queries setObject: context + forKey: context->_ID]; [context->_cancelTimer invalidate]; [context->_cancelTimer release]; context->_cancelTimer = nil; context->_cancelTimer = [[OFTimer alloc] @@ -709,10 +733,25 @@ [[OFRunLoop currentRunLoop] addTimer: context->_cancelTimer forMode: runLoopMode]; nameServer = [context->_settings->_nameServers objectAtIndex: context->_nameServersIndex]; + + if (context->_settings->_usesTCP) { + OF_ENSURE(context->_TCPSocket == nil); + + context->_TCPSocket = [[OFTCPSocket alloc] init]; + [_TCPQueries setObject: context + forKey: context->_TCPSocket]; + + context->_TCPSocket.delegate = self; + [context->_TCPSocket asyncConnectToHost: nameServer + port: 53 + runLoopMode: runLoopMode]; + return; + } + context->_usedNameServer = of_socket_address_parse_ip(nameServer, 53); switch (context->_usedNameServer.family) { #ifdef OF_HAVE_IPV6 case OF_SOCKET_ADDRESS_FAMILY_IPV6: @@ -721,11 +760,11 @@ of_socket_address_parse_ip(@"::", 0); _IPv6Socket = [[OFUDPSocket alloc] init]; [_IPv6Socket of_bindToAddress: &address extraType: SOCK_DNS]; - _IPv6Socket.blocking = false; + _IPv6Socket.canBlock = false; _IPv6Socket.delegate = self; } sock = _IPv6Socket; break; @@ -736,11 +775,11 @@ of_socket_address_parse_ip(@"0.0.0.0", 0); _IPv4Socket = [[OFUDPSocket alloc] init]; [_IPv4Socket of_bindToAddress: &address extraType: SOCK_DNS]; - _IPv4Socket.blocking = false; + _IPv4Socket.canBlock = false; _IPv4Socket.delegate = self; } sock = _IPv4Socket; break; @@ -783,13 +822,10 @@ context = [[[OFDNSResolverContext alloc] initWithQuery: query ID: ID settings: _settings delegate: delegate] autorelease]; - [_queries setObject: context - forKey: ID]; - [self of_sendQueryForContext: context runLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } @@ -796,21 +832,30 @@ - (void)of_contextTimedOut: (OFDNSResolverContext *)context { of_run_loop_mode_t runLoopMode = [OFRunLoop currentRunLoop].currentMode; OFDNSQueryFailedException *exception; + + if (context->_TCPSocket != nil) { + context->_TCPSocket.delegate = nil; + [context->_TCPSocket cancelAsyncRequests]; + + [_TCPQueries removeObjectForKey: context->_TCPSocket]; + [context->_TCPSocket release]; + context->_TCPSocket = nil; + context->_responseLength = 0; + } if (context->_nameServersIndex + 1 < context->_settings->_nameServers.count) { context->_nameServersIndex++; [self of_sendQueryForContext: context runLoopMode: runLoopMode]; return; } - if (context->_attempt < context->_settings->_maxAttempts) { - context->_attempt++; + if (++context->_attempt < context->_settings->_maxAttempts) { context->_nameServersIndex = 0; [self of_sendQueryForContext: context runLoopMode: runLoopMode]; return; } @@ -841,26 +886,21 @@ didPerformQuery: context->_query response: nil exception: exception]; } -- (bool)socket: (OFUDPSocket *)sock - didReceiveIntoBuffer: (void *)buffer_ - length: (size_t)length - sender: (const of_socket_address_t *)sender - exception: (id)exception +- (bool)of_handleResponseBuffer: (unsigned char *)buffer + length: (size_t)length + sender: (const of_socket_address_t *)sender { - unsigned char *buffer = buffer_; OFDictionary *answerRecords = nil, *authorityRecords = nil; OFDictionary *additionalRecords = nil; OFDNSResponse *response = nil; + id exception = nil; OFNumber *ID; OFDNSResolverContext *context; - if (exception != nil) - return true; - if (length < 2) /* We can't get the ID to get the context. Ignore packet. */ return true; ID = [OFNumber numberWithUInt16: (buffer[0] << 8) | buffer[1]]; @@ -867,11 +907,14 @@ context = [[[_queries objectForKey: ID] retain] autorelease]; if (context == nil) return true; - if (!of_socket_address_equal(sender, &context->_usedNameServer)) + if (context->_TCPSocket != nil) { + if ([_TCPQueries objectForKey: context->_TCPSocket] != context) + return true; + } else if (!of_socket_address_equal(sender, &context->_usedNameServer)) return true; [context->_cancelTimer invalidate]; [context->_cancelTimer release]; context->_cancelTimer = nil; @@ -901,12 +944,22 @@ /* Opcode */ if ((buffer[2] & 0x78) != (queryDataBuffer[2] & 0x78)) @throw [OFInvalidServerReplyException exception]; /* TC */ - if (buffer[2] & 0x02) - @throw [OFTruncatedDataException exception]; + if (buffer[2] & 0x02) { + of_run_loop_mode_t runLoopMode; + + if (context->_settings->_usesTCP) + @throw [OFTruncatedDataException exception]; + + context->_settings->_usesTCP = true; + runLoopMode = [OFRunLoop currentRunLoop].currentMode; + [self of_sendQueryForContext: context + runLoopMode: runLoopMode]; + return false; + } /* RCODE */ switch (buffer[3] & 0x0F) { case 0: break; @@ -993,10 +1046,152 @@ [context->_delegate resolver: self didPerformQuery: context->_query response: response exception: exception]; + return false; +} + +- (bool)socket: (OFDatagramSocket *)sock + didReceiveIntoBuffer: (void *)buffer + length: (size_t)length + sender: (const of_socket_address_t *)sender + exception: (id)exception +{ + if (exception != nil) + return true; + + return [self of_handleResponseBuffer: buffer + length: length + sender: sender]; +} + +- (void)socket: (OFTCPSocket *)sock + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (id)exception +{ + OFDNSResolverContext *context = [_TCPQueries objectForKey: sock]; + + OF_ENSURE(context != nil); + + if (exception != nil) { + /* + * TODO: Handle error immediately instead of waiting for the + * timer to try the next nameserver or to retry. + */ + [_TCPQueries removeObjectForKey: context->_TCPSocket]; + [context->_TCPSocket release]; + context->_TCPSocket = nil; + context->_responseLength = 0; + return; + } + + if (context->_TCPQueryData == nil) { + size_t queryDataCount = context->_queryData.count; + uint16_t tmp; + + if (queryDataCount > UINT16_MAX) + @throw [OFOutOfRangeException exception]; + + context->_TCPQueryData = [[OFMutableData alloc] + initWithCapacity: queryDataCount + 2]; + + tmp = OF_BSWAP16_IF_LE(queryDataCount); + [context->_TCPQueryData addItems: &tmp + count: sizeof(tmp)]; + [context->_TCPQueryData addItems: context->_queryData.items + count: queryDataCount]; + } + + [sock asyncWriteData: context->_TCPQueryData]; +} + +- (OFData *)stream: (OFStream *)stream + didWriteData: (OFData *)data + bytesWritten: (size_t)bytesWritten + exception: (id)exception +{ + OFTCPSocket *sock = (OFTCPSocket *)stream; + OFDNSResolverContext *context = [_TCPQueries objectForKey: sock]; + + OF_ENSURE(context != nil); + + if (exception != nil) { + /* + * TODO: Handle error immediately instead of waiting for the + * timer to try the next nameserver or to retry. + */ + [_TCPQueries removeObjectForKey: context->_TCPSocket]; + [context->_TCPSocket release]; + context->_TCPSocket = nil; + context->_responseLength = 0; + return nil; + } + + if (context->_TCPBuffer == nil) + context->_TCPBuffer = + [context allocMemoryWithSize: MAX_DNS_RESPONSE_LENGTH]; + + [sock asyncReadIntoBuffer: context->_TCPBuffer + exactLength: 2]; + return nil; +} + +- (bool)stream: (OFStream *)stream + didReadIntoBuffer: (void *)buffer + length: (size_t)length + exception: (id)exception +{ + OFTCPSocket *sock = (OFTCPSocket *)stream; + OFDNSResolverContext *context = [_TCPQueries objectForKey: sock]; + + OF_ENSURE(context != nil); + + if (exception != nil) { + /* + * TODO: Handle error immediately instead of waiting for the + * timer to try the next nameserver or to retry. + */ + goto done; + } + + if (context->_responseLength == 0) { + unsigned char *ucBuffer = buffer; + + OF_ENSURE(length == 2); + + context->_responseLength = (ucBuffer[0] << 8) | ucBuffer[1]; + + if (context->_responseLength > MAX_DNS_RESPONSE_LENGTH) + @throw [OFOutOfRangeException exception]; + + if (context->_responseLength == 0) + goto done; + + [sock asyncReadIntoBuffer: context->_TCPBuffer + exactLength: context->_responseLength]; + return false; + } + + if (length != context->_responseLength) + /* + * The connection was closed before we received the entire + * response. + */ + goto done; + + [self of_handleResponseBuffer: buffer + length: length + sender: NULL]; + +done: + [_TCPQueries removeObjectForKey: context->_TCPSocket]; + [context->_TCPSocket release]; + context->_TCPSocket = nil; + context->_responseLength = 0; + return false; } - (void)asyncResolveAddressesForHost: (OFString *)host delegate: (id )delegate Index: src/OFDNSResolverSettings.m ================================================================== --- src/OFDNSResolverSettings.m +++ src/OFDNSResolverSettings.m @@ -40,11 +40,14 @@ # include # undef interface #endif #ifdef OF_NINTENDO_3DS +/* Newer versions of libctru started using id as a parameter name. */ +# define id id_3ds # include <3ds.h> +# undef id #endif #import "socket_helpers.h" #if defined(OF_HAIKU) @@ -483,15 +486,17 @@ [self setDefaults]; #if defined(OF_WINDOWS) # ifdef OF_HAVE_FILES - path = [[OFWindowsRegistryKey localMachineKey] - stringForValue: @"DataBasePath" - subkeyPath: @"SYSTEM\\CurrentControlSet\\Services\\" - @"Tcpip\\Parameters"]; - path = [path stringByAppendingPathComponent: @"hosts"]; + OFWindowsRegistryKey *key = [[OFWindowsRegistryKey localMachineKey] + openSubkeyAtPath: @"SYSTEM\\CurrentControlSet\\Services\\" + @"Tcpip\\Parameters" + securityAndAccessRights: KEY_QUERY_VALUE]; + path = [[[key stringForValue: @"DataBasePath"] + stringByAppendingPathComponent: @"hosts"] + stringByExpandingWindowsEnvironmentStrings]; if (path != nil) [self parseHosts: path]; # endif Index: src/OFDNSResourceRecord.h ================================================================== --- src/OFDNSResourceRecord.h +++ src/OFDNSResourceRecord.h @@ -22,10 +22,11 @@ OF_ASSUME_NONNULL_BEGIN /*! @file */ +@class OFArray OF_GENERIC(ObjectType); @class OFData; /*! * @brief The DNS class. */ @@ -588,17 +589,17 @@ * @brief A class representing a TXT DNS resource record. */ OF_SUBCLASSING_RESTRICTED @interface OFTXTDNSResourceRecord: OFDNSResourceRecord { - OFData *_textData; + OFArray OF_GENERIC(OFData *) *_textStrings; } /*! * @brief The text of the resource record. */ -@property (readonly, nonatomic) OFData *textData; +@property (readonly, nonatomic) OFArray OF_GENERIC(OFData *) *textStrings; - (instancetype)initWithName: (OFString *)name DNSClass: (of_dns_class_t)DNSClass recordType: (of_dns_record_type_t)recordType TTL: (uint32_t)TTL OF_UNAVAILABLE; @@ -607,17 +608,17 @@ * @brief Initializes an already allocated OFTXTDNSResourceRecord with the * specified name, class, text data and time to live. * * @param name The name for the resource record * @param DNSClass The class code for the resource record - * @param textData The data for the resource record + * @param textStrings An array of text strings for the resource record * @param TTL The time to live for the resource record * @return An initialized OFTXTDNSResourceRecord */ - (instancetype)initWithName: (OFString *)name DNSClass: (of_dns_class_t)DNSClass - textData: (OFData *)textData + textStrings: (OFArray OF_GENERIC(OFData *) *)textStrings TTL: (uint32_t)TTL OF_DESIGNATED_INITIALIZER; @end #ifdef __cplusplus extern "C" { Index: src/OFDNSResourceRecord.m ================================================================== --- src/OFDNSResourceRecord.m +++ src/OFDNSResourceRecord.m @@ -16,10 +16,11 @@ */ #include "config.h" #import "OFDNSResourceRecord.h" +#import "OFArray.h" #import "OFData.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" @@ -78,11 +79,11 @@ if ([string isEqual: @"IN"]) DNSClass = OF_DNS_CLASS_IN; else { @try { - DNSClass = (of_dns_class_t)[string decimalValue]; + DNSClass = (of_dns_class_t)string.decimalValue; } @catch (OFInvalidFormatException *e) { @throw [OFInvalidArgumentException exception]; } } @@ -118,14 +119,16 @@ recordType = OF_DNS_RECORD_TYPE_RP; else if ([string isEqual: @"AAAA"]) recordType = OF_DNS_RECORD_TYPE_AAAA; else if ([string isEqual: @"SRV"]) recordType = OF_DNS_RECORD_TYPE_SRV; + else if ([string isEqual: @"ALL"]) + recordType = OF_DNS_RECORD_TYPE_ALL; else { @try { recordType = - (of_dns_record_type_t)[string decimalValue]; + (of_dns_record_type_t)string.decimalValue; } @catch (OFInvalidFormatException *e) { @throw [OFInvalidArgumentException exception]; } } @@ -1248,11 +1251,11 @@ self.className, _name, _priority, _weight, _target, _port, _TTL]; } @end @implementation OFTXTDNSResourceRecord -@synthesize textData = _textData; +@synthesize textStrings = _textStrings; - (instancetype)initWithName: (OFString *)name DNSClass: (of_dns_class_t)DNSClass recordType: (of_dns_record_type_t)recordType TTL: (uint32_t)TTL @@ -1260,20 +1263,20 @@ OF_INVALID_INIT_METHOD } - (instancetype)initWithName: (OFString *)name DNSClass: (of_dns_class_t)DNSClass - textData: (OFData *)textData + textStrings: (OFArray OF_GENERIC(OFData *) *)textStrings TTL: (uint32_t)TTL { self = [super initWithName: name DNSClass: DNSClass recordType: OF_DNS_RECORD_TYPE_TXT TTL: TTL]; @try { - _textData = [textData copy]; + _textStrings = [textStrings copy]; } @catch (id e) { [self release]; @throw e; } @@ -1280,11 +1283,11 @@ return self; } - (void)dealloc { - [_textData release]; + [_textStrings release]; [super dealloc]; } - (bool)isEqual: (id)object @@ -1306,12 +1309,12 @@ return false; if (record->_recordType != _recordType) return false; - if (record->_textData != _textData && - ![record->_textData isEqual: _textData]) + if (record->_textStrings != _textStrings && + ![record->_textStrings isEqual: _textStrings]) return false; return true; } @@ -1324,25 +1327,62 @@ OF_HASH_ADD_HASH(hash, _name.hash); OF_HASH_ADD(hash, _DNSClass >> 8); OF_HASH_ADD(hash, _DNSClass); OF_HASH_ADD(hash, _recordType >> 8); OF_HASH_ADD(hash, _recordType); - OF_HASH_ADD_HASH(hash, _textData.hash); + OF_HASH_ADD_HASH(hash, _textStrings.hash); OF_HASH_FINALIZE(hash); return hash; } - (OFString *)description { - return [OFString stringWithFormat: + void *pool = objc_autoreleasePoolPush(); + OFMutableString *text = [OFMutableString string]; + bool first = true; + OFString *ret; + + for (OFData *string in _textStrings) { + const unsigned char *stringItems = string.items; + size_t stringCount = string.count; + + if (first) { + first = false; + [text appendString: @"\""]; + } else + [text appendString: @" \""]; + + for (size_t i = 0; i < stringCount; i++) { + if (stringItems[i] == '\\') + [text appendString: @"\\\\"]; + else if (stringItems[i] == '"') + [text appendString: @"\\\""]; + else if (stringItems[i] < 0x20) + [text appendFormat: @"\\x%02X", stringItems[i]]; + else if (stringItems[i] < 0x7F) + [text appendFormat: @"%c", stringItems[i]]; + else + [text appendFormat: @"\\x%02X", stringItems[i]]; + } + + [text appendString: @"\""]; + } + + ret = [OFString stringWithFormat: @"<%@:\n" @"\tName = %@\n" @"\tClass = %@\n" - @"\tText Data = %@\n" + @"\tText strings = %@\n" @"\tTTL = %" PRIu32 "\n" @">", - self.className, _name, of_dns_class_to_string(_DNSClass), _textData, + self.className, _name, of_dns_class_to_string(_DNSClass), text, _TTL]; + + [ret retain]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; } @end Index: src/OFDNSResponse.h ================================================================== --- src/OFDNSResponse.h +++ src/OFDNSResponse.h @@ -70,10 +70,11 @@ @property (readonly, nonatomic) of_dns_response_records_t additionalRecords; /*! * @brief Creates a new, autoreleased OFDNSResponse. * + * @param domainName The domain name the response is for * @param answerRecords The answer records of the response * @param authorityRecords The authority records of the response * @param additionalRecords The additional records of the response * @return A new, autoreleased OFDNSResponse */ @@ -84,10 +85,11 @@ additionalRecords: (of_dns_response_records_t)additionalRecords; /*! * @brief Initializes an already allocated OFDNSResponse. * + * @param domainName The domain name the response is for * @param answerRecords The answer records of the response * @param authorityRecords The authority records of the response * @param additionalRecords The additional records of the response * @return An initialized OFDNSResponse */ Index: src/OFData+MessagePackValue.m ================================================================== --- src/OFData+MessagePackValue.m +++ src/OFData+MessagePackValue.m @@ -278,35 +278,28 @@ *object = [OFNumber numberWithInt64: readUInt64(buffer + 1)]; return 9; /* Floating point */ case 0xCA:; /* float 32 */ - union { - unsigned char u8[4]; - float f; - } f; + float f; if (length < 5) @throw [OFTruncatedDataException exception]; - memcpy(&f.u8, buffer + 1, 4); + memcpy(&f, buffer + 1, 4); - *object = [OFNumber numberWithFloat: OF_BSWAP_FLOAT_IF_LE(f.f)]; + *object = [OFNumber numberWithFloat: OF_BSWAP_FLOAT_IF_LE(f)]; return 5; case 0xCB:; /* float 64 */ - union { - unsigned char u8[8]; - double d; - } d; + double d; if (length < 9) @throw [OFTruncatedDataException exception]; - memcpy(&d.u8, buffer + 1, 8); + memcpy(&d, buffer + 1, 8); - *object = [OFNumber numberWithDouble: - OF_BSWAP_DOUBLE_IF_LE(d.d)]; + *object = [OFNumber numberWithDouble: OF_BSWAP_DOUBLE_IF_LE(d)]; return 9; /* nil */ case 0xC0: *object = [OFNull null]; return 1; ADDED src/OFDatagramSocket.h Index: src/OFDatagramSocket.h ================================================================== --- src/OFDatagramSocket.h +++ src/OFDatagramSocket.h @@ -0,0 +1,312 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFObject.h" +#import "OFKernelEventObserver.h" +#import "OFRunLoop.h" + +#import "socket.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFData; +@class OFDatagramSocket; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when a packet has been received. + * + * @param length The length of the packet + * @param sender The address of the sender of the packet + * @param exception An exception which occurred while receiving or `nil` on + * success + * @return A bool whether the same block should be used for the next receive + */ +typedef bool (^of_datagram_socket_async_receive_block_t)( + size_t length, const of_socket_address_t *_Nonnull sender, + id _Nullable exception); + +/*! + * @brief A block which is called when a packet has been sent. + * + * @param data The data which was sent + * @param receiver The receiver for the packet + * @param exception An exception which occurred while reading or `nil` on + * success + * @return The data to repeat the send with or nil if it should not repeat + */ +typedef OFData *_Nullable (^of_datagram_socket_async_send_data_block_t)( + OFData *_Nonnull data, const of_socket_address_t *_Nonnull receiver, + id _Nullable exception); +#endif + +/*! + * @protocol OFDatagramSocketDelegate OFDatagramSocket.h \ + * ObjFW/OFDatagramSocket.h + * + * @brief A delegate for OFDatagramSocket. + */ +@protocol OFDatagramSocketDelegate +@optional +/*! + * @brief This method is called when a packet has been received. + * + * @param socket The datagram socket which received a packet + * @param buffer The buffer the packet has been written to + * @param length The length of the packet + * @param sender The address of the sender of the packet + * @param exception An exception that occurred while receiving, or nil on + * success + * @return A bool whether the same block should be used for the next receive + */ +- (bool)socket: (OFDatagramSocket *)socket + didReceiveIntoBuffer: (void *)buffer + length: (size_t)length + sender: (const of_socket_address_t *_Nonnull)sender + exception: (nullable id)exception; + +/*! + * @brief This method is called when a packet has been sent. + * + * @param socket The datagram socket which sent a packet + * @param data The data which was sent + * @param receiver The receiver for the packet + * @param exception An exception that occurred while sending, or nil on success + * @return The data to repeat the send with or nil if it should not repeat + */ +- (nullable OFData *)socket: (OFDatagramSocket *)socket + didSendData: (OFData *)data + receiver: (const of_socket_address_t *_Nonnull)receiver + exception: (nullable id)exception; +@end + +/*! + * @class OFDatagramSocket OFDatagramSocket.h ObjFW/OFDatagramSocket.h + * + * @brief A base class for datagram sockets. + * + * @warning Even though the OFCopying protocol is implemented, it does *not* + * return an independent copy of the socket, but instead retains it. + * This is so that the socket can be used as a key for a dictionary, + * so context can be associated with a socket. Using a socket in more + * than one thread at the same time is not thread-safe, even if copy + * was called to create one "instance" for every thread! + */ +@interface OFDatagramSocket: OFObject +{ + of_socket_t _socket; + bool _canBlock; +#ifdef OF_WII + bool _canSendToBroadcastAddresses; +#endif + id _Nullable _delegate; + OF_RESERVE_IVARS(4) +} + +/*! + * @brief Whether the socket can block. + * + * By default, a socket can block. + */ +@property (nonatomic) bool canBlock; + +/*! + * @brief Whether the socket can send to broadcast addresses. + */ +@property (nonatomic) bool canSendToBroadcastAddresses; + +/*! + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Returns a new, autoreleased OFDatagramSocket. + * + * @return A new, autoreleased OFDatagramSocket + */ ++ (instancetype)socket; + +/*! + * @brief Receives a datagram and stores it into the specified buffer. + * + * If the buffer is too small, the datagram is truncated. + * + * @param buffer The buffer to write the datagram to + * @param length The length of the buffer + * @param sender A pointer to an @ref of_socket_address_t, which will be set to + * the address of the sender + * @return The length of the received datagram + */ +- (size_t)receiveIntoBuffer: (void *)buffer + length: (size_t)length + sender: (of_socket_address_t *)sender; + +/*! + * @brief Asynchronously receives a datagram and stores it into the specified + * buffer. + * + * If the buffer is too small, the datagram is truncated. + * + * @param buffer The buffer to write the datagram to + * @param length The length of the buffer + */ +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length; + +/*! + * @brief Asynchronously receives a datagram and stores it into the specified + * buffer. + * + * If the buffer is too small, the datagram is truncated. + * + * @param buffer The buffer to write the datagram to + * @param length The length of the buffer + * @param runLoopMode The run loop mode in which to perform the async receive + */ +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously receives a datagram and stores it into the specified + * buffer. + * + * If the buffer is too small, the datagram is truncated. + * + * @param buffer The buffer to write the datagram to + * @param length The length of the buffer + * @param block The block to call when the datagram has been received. If the + * block returns true, it will be called again with the same + * buffer and maximum length when more datagrams have been + * received. If you want the next method in the queue to handle + * the datagram received next, you need to return false from the + * method. + */ +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + block: (of_datagram_socket_async_receive_block_t)block; + +/*! + * @brief Asynchronously receives a datagram and stores it into the specified + * buffer. + * + * If the buffer is too small, the datagram is truncated. + * + * @param buffer The buffer to write the datagram to + * @param length The length of the buffer + * @param runLoopMode The run loop mode in which to perform the async receive + * @param block The block to call when the datagram has been received. If the + * block returns true, it will be called again with the same + * buffer and maximum length when more datagrams have been + * received. If you want the next method in the queue to handle + * the datagram received next, you need to return false from the + * method. + */ +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_datagram_socket_async_receive_block_t)block; +#endif + +/*! + * @brief Sends the specified datagram to the specified address. + * + * @param buffer The buffer to send as a datagram + * @param length The length of the buffer + * @param receiver A pointer to an @ref of_socket_address_t to which the + * datagram should be sent + */ +- (void)sendBuffer: (const void *)buffer + length: (size_t)length + receiver: (const of_socket_address_t *)receiver; + +/*! + * @brief Asynchronously sends the specified datagram to the specified address. + * + * @param data The data to send as a datagram + * @param receiver A pointer to an @ref of_socket_address_t to which the + * datagram should be sent. The receiver is copied. + */ +- (void)asyncSendData: (OFData *)data + receiver: (const of_socket_address_t *)receiver; + +/*! + * @brief Asynchronously sends the specified datagram to the specified address. + * + * @param data The data to send as a datagram + * @param receiver A pointer to an @ref of_socket_address_t to which the + * datagram should be sent. The receiver is copied. + * @param runLoopMode The run loop mode in which to perform the async send + */ +- (void)asyncSendData: (OFData *)data + receiver: (const of_socket_address_t *)receiver + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously sends the specified datagram to the specified address. + * + * @param data The data to send as a datagram + * @param receiver A pointer to an @ref of_socket_address_t to which the + * datagram should be sent. The receiver is copied. + * @param block The block to call when the packet has been sent. It should + * return the data for the next send with the same callback or nil + * if it should not repeat. + */ +- (void)asyncSendData: (OFData *)data + receiver: (const of_socket_address_t *)receiver + block: (of_datagram_socket_async_send_data_block_t)block; + +/*! + * @brief Asynchronously sends the specified datagram to the specified address. + * + * @param data The data to send as a datagram + * @param receiver A pointer to an @ref of_socket_address_t to which the + * datagram should be sent. The receiver is copied. + * @param runLoopMode The run loop mode in which to perform the async send + * @param block The block to call when the packet has been sent. It should + * return the data for the next send with the same callback or nil + * if it should not repeat. + */ +- (void)asyncSendData: (OFData *)data + receiver: (const of_socket_address_t *)receiver + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_datagram_socket_async_send_data_block_t)block; +#endif + +/*! + * @brief Cancels all pending asynchronous requests on the socket. + */ +- (void)cancelAsyncRequests; + +/*! + * @brief Closes the socket so that it can neither receive nor send any more + * datagrams. + */ +- (void)close; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFDatagramSocket.m Index: src/OFDatagramSocket.m ================================================================== --- src/OFDatagramSocket.m +++ src/OFDatagramSocket.m @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFDatagramSocket.h" +#import "OFData.h" +#import "OFRunLoop+Private.h" +#import "OFRunLoop.h" + +#import "OFGetOptionFailedException.h" +#import "OFInitializationFailedException.h" +#import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" +#import "OFSetOptionFailedException.h" +#import "OFSetOptionFailedException.h" +#import "OFWriteFailedException.h" + +#import "socket.h" +#import "socket_helpers.h" + +@implementation OFDatagramSocket +@synthesize delegate = _delegate; + ++ (void)initialize +{ + if (self != [OFDatagramSocket class]) + return; + + if (!of_socket_init()) + @throw [OFInitializationFailedException + exceptionWithClass: self]; +} + ++ (instancetype)socket +{ + return [[[self alloc] init] autorelease]; +} + +- (instancetype)init +{ + self = [super init]; + + @try { + if (self.class == [OFDatagramSocket class]) { + [self doesNotRecognizeSelector: _cmd]; + abort(); + } + + _socket = INVALID_SOCKET; + _canBlock = true; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_socket != INVALID_SOCKET) + [self close]; + + [super dealloc]; +} + +- (id)copy +{ + return [self retain]; +} + +- (bool)canBlock +{ + return _canBlock; +} + +- (void)setCanBlock: (bool)canBlock +{ +#if defined(HAVE_FCNTL) + int flags = fcntl(_socket, F_GETFL, 0); + + if (flags == -1) + @throw [OFSetOptionFailedException exceptionWithObject: self + errNo: errno]; + + if (canBlock) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(_socket, F_SETFL, flags) == -1) + @throw [OFSetOptionFailedException exceptionWithObject: self + errNo: errno]; + + _canBlock = canBlock; +#elif defined(OF_WINDOWS) + u_long v = canBlock; + + if (ioctlsocket(_socket, FIONBIO, &v) == SOCKET_ERROR) + @throw [OFSetOptionFailedException + exceptionWithObject: self + errNo: of_socket_errno()]; + + _canBlock = canBlock; +#else + OF_UNRECOGNIZED_SELECTOR +#endif +} + +- (void)setCanSendToBroadcastAddresses: (bool)canSendToBroadcastAddresses +{ + int v = canSendToBroadcastAddresses; + + if (setsockopt(_socket, SOL_SOCKET, SO_BROADCAST, + (char *)&v, (socklen_t)sizeof(v)) != 0) + @throw [OFSetOptionFailedException + exceptionWithObject: self + errNo: of_socket_errno()]; + +#ifdef OF_WII + _canSendToBroadcastAddresses = canSendToBroadcastAddresses; +#endif +} + +- (bool)canSendToBroadcastAddresses +{ +#ifndef OF_WII + int v; + socklen_t len = sizeof(v); + + if (getsockopt(_socket, SOL_SOCKET, SO_BROADCAST, + (char *)&v, &len) != 0 || len != sizeof(v)) + @throw [OFGetOptionFailedException + exceptionWithObject: self + errNo: of_socket_errno()]; + + return v; +#else + return _canSendToBroadcastAddresses; +#endif +} + +- (size_t)receiveIntoBuffer: (void *)buffer + length: (size_t)length + sender: (of_socket_address_t *)sender +{ + ssize_t ret; + + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + sender->length = (socklen_t)sizeof(sender->sockaddr); + +#ifndef OF_WINDOWS + if ((ret = recvfrom(_socket, buffer, length, 0, + &sender->sockaddr.sockaddr, &sender->length)) < 0) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: length + errNo: of_socket_errno()]; +#else + if (length > INT_MAX) + @throw [OFOutOfRangeException exception]; + + if ((ret = recvfrom(_socket, buffer, (int)length, 0, + &sender->sockaddr.sockaddr, &sender->length)) < 0) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: length + errNo: of_socket_errno()]; +#endif + + switch (sender->sockaddr.sockaddr.sa_family) { + case AF_INET: + sender->family = OF_SOCKET_ADDRESS_FAMILY_IPV4; + break; +#ifdef OF_HAVE_IPV6 + case AF_INET6: + sender->family = OF_SOCKET_ADDRESS_FAMILY_IPV6; + break; +#endif +#ifdef OF_HAVE_IPX + case AF_IPX: + sender->family = OF_SOCKET_ADDRESS_FAMILY_IPX; + break; +#endif + default: + sender->family = OF_SOCKET_ADDRESS_FAMILY_UNKNOWN; + break; + } + + return ret; +} + +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length +{ + [self asyncReceiveIntoBuffer: buffer + length: length + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + [OFRunLoop of_addAsyncReceiveForDatagramSocket: self + buffer: buffer + length: length + mode: runLoopMode +# ifdef OF_HAVE_BLOCKS + block: NULL +# endif + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + block: (of_datagram_socket_async_receive_block_t)block +{ + [self asyncReceiveIntoBuffer: buffer + length: length + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_datagram_socket_async_receive_block_t)block +{ + [OFRunLoop of_addAsyncReceiveForDatagramSocket: self + buffer: buffer + length: length + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (void)sendBuffer: (const void *)buffer + length: (size_t)length + receiver: (const of_socket_address_t *)receiver +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + +#ifndef OF_WINDOWS + ssize_t bytesWritten; + + if (length > SSIZE_MAX) + @throw [OFOutOfRangeException exception]; + + if ((bytesWritten = sendto(_socket, (void *)buffer, length, 0, + (struct sockaddr *)&receiver->sockaddr.sockaddr, + receiver->length)) < 0) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: of_socket_errno()]; +#else + int bytesWritten; + + if (length > INT_MAX) + @throw [OFOutOfRangeException exception]; + + if ((bytesWritten = sendto(_socket, buffer, (int)length, 0, + &receiver->sockaddr.sockaddr, receiver->length)) < 0) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: of_socket_errno()]; +#endif + + if ((size_t)bytesWritten != length) + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: bytesWritten + errNo: 0]; +} + +- (void)asyncSendData: (OFData *)data + receiver: (const of_socket_address_t *)receiver +{ + [self asyncSendData: data + receiver: receiver + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncSendData: (OFData *)data + receiver: (const of_socket_address_t *)receiver + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + [OFRunLoop of_addAsyncSendForDatagramSocket: self + data: data + receiver: receiver + mode: runLoopMode +# ifdef OF_HAVE_BLOCKS + block: NULL +# endif + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncSendData: (OFData *)data + receiver: (const of_socket_address_t *)receiver + block: (of_datagram_socket_async_send_data_block_t)block +{ + [self asyncSendData: data + receiver: receiver + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncSendData: (OFData *)data + receiver: (const of_socket_address_t *)receiver + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_datagram_socket_async_send_data_block_t)block +{ + [OFRunLoop of_addAsyncSendForDatagramSocket: self + data: data + receiver: receiver + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (void)cancelAsyncRequests +{ + [OFRunLoop of_cancelAsyncRequestsForObject: self + mode: of_run_loop_mode_default]; +} + +- (int)fileDescriptorForReading +{ +#ifndef OF_WINDOWS + return _socket; +#else + if (_socket == INVALID_SOCKET) + return -1; + + if (_socket > INT_MAX) + @throw [OFOutOfRangeException exception]; + + return (int)_socket; +#endif +} + +- (int)fileDescriptorForWriting +{ +#ifndef OF_WINDOWS + return _socket; +#else + if (_socket == INVALID_SOCKET) + return -1; + + if (_socket > INT_MAX) + @throw [OFOutOfRangeException exception]; + + return (int)_socket; +#endif +} + +- (void)close +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + closesocket(_socket); + _socket = INVALID_SOCKET; +} +@end Index: src/OFDate.m ================================================================== --- src/OFDate.m +++ src/OFDate.m @@ -48,10 +48,14 @@ #if (!defined(HAVE_GMTIME_R) || !defined(HAVE_LOCALTIME_R)) && \ defined(OF_HAVE_THREADS) static OFMutex *mutex; #endif + +#ifdef OF_WINDOWS +static __time64_t (*func__mktime64)(struct tm *); +#endif #ifdef HAVE_GMTIME_R # define GMTIME_RET(field) \ time_t seconds = (time_t)_seconds; \ struct tm tm; \ @@ -184,18 +188,30 @@ return seconds; } @implementation OFDate -#if (!defined(HAVE_GMTIME_R) || !defined(HAVE_LOCALTIME_R)) && \ - defined(OF_HAVE_THREADS) + (void)initialize { - if (self == [OFDate class]) - mutex = [[OFMutex alloc] init]; -} +#ifdef OF_WINDOWS + HMODULE module; +#endif + + if (self != [OFDate class]) + return; + +#if (!defined(HAVE_GMTIME_R) || !defined(HAVE_LOCALTIME_R)) && \ + defined(OF_HAVE_THREADS) + mutex = [[OFMutex alloc] init]; +#endif + +#ifdef OF_WINDOWS + if ((module = LoadLibrary("msvcrt.dll")) != NULL) + func__mktime64 = (__time64_t (*)(struct tm *)) + GetProcAddress(module, "_mktime64"); #endif +} + (instancetype)date { return [[[self alloc] init] autorelease]; } @@ -315,16 +331,22 @@ if (of_strptime(UTF8String, format.UTF8String, &tm, &tz) != UTF8String + string.UTF8StringLength) @throw [OFInvalidFormatException exception]; if (tz == INT16_MAX) { -#ifndef OF_WINDOWS - if ((_seconds = mktime(&tm)) == -1) - @throw [OFInvalidFormatException exception]; -#else - if ((_seconds = _mktime64(&tm)) == -1) - @throw [OFInvalidFormatException exception]; +#ifdef OF_WINDOWS + if (func__mktime64 != NULL) { + if ((_seconds = func__mktime64(&tm)) == -1) + @throw [OFInvalidFormatException + exception]; + } else { +#endif + if ((_seconds = mktime(&tm)) == -1) + @throw [OFInvalidFormatException + exception]; +#ifdef OF_WINDOWS + } #endif } else _seconds = tmAndTzToTime(&tm, &tz); } @catch (id e) { [self release]; @@ -338,22 +360,17 @@ { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); - union { - double d; - uint64_t u; - } d; if (![element.name isEqual: self.className] || ![element.namespace isEqual: OF_SERIALIZATION_NS]) @throw [OFInvalidArgumentException exception]; - d.u = (uint64_t)element.hexadecimalValue; - d.u = OF_BSWAP64_IF_LE(d.u); - _seconds = OF_BSWAP_DOUBLE_IF_LE(d.d); + _seconds = OF_BSWAP_DOUBLE_IF_LE(OF_INT_TO_DOUBLE_RAW( + OF_BSWAP64_IF_LE(element.hexadecimalValue))); objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; @@ -381,21 +398,18 @@ } - (uint32_t)hash { uint32_t hash; - union { - double d; - uint8_t b[sizeof(double)]; - } d; - - d.d = OF_BSWAP_DOUBLE_IF_BE(_seconds); + double tmp; OF_HASH_INIT(hash); + + tmp = OF_BSWAP_DOUBLE_IF_BE(_seconds); for (size_t i = 0; i < sizeof(double); i++) - OF_HASH_ADD(hash, d.b[i]); + OF_HASH_ADD(hash, ((char *)&tmp)[i]); OF_HASH_FINALIZE(hash); return hash; } @@ -429,21 +443,17 @@ - (OFXMLElement *)XMLElementBySerializing { void *pool = objc_autoreleasePoolPush(); OFXMLElement *element; - union { - double d; - uint64_t u; - } d; element = [OFXMLElement elementWithName: self.className namespace: OF_SERIALIZATION_NS]; - d.d = OF_BSWAP_DOUBLE_IF_LE(_seconds); - element.stringValue = - [OFString stringWithFormat: @"%016" PRIx64, OF_BSWAP64_IF_LE(d.u)]; + element.stringValue = [OFString stringWithFormat: @"%016" PRIx64, + OF_BSWAP64_IF_LE(OF_DOUBLE_TO_INT_RAW(OF_BSWAP_DOUBLE_IF_LE( + _seconds)))]; [element retain]; objc_autoreleasePoolPop(pool); Index: src/OFFile.m ================================================================== --- src/OFFile.m +++ src/OFFile.m @@ -30,10 +30,11 @@ #endif #import "OFFile.h" #import "OFLocale.h" #import "OFString.h" +#import "OFSystemInfo.h" #import "OFURL.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" #import "OFNotOpenException.h" @@ -218,20 +219,27 @@ if ((flags = parseMode(mode.UTF8String)) == -1) @throw [OFInvalidArgumentException exception]; flags |= O_BINARY | O_CLOEXEC; -# if defined(OF_WINDOWS) - if ((handle = _wopen(path.UTF16String, flags, - _S_IREAD | _S_IWRITE)) == -1) -# elif defined(HAVE_OPEN64) - if ((handle = open64([path cStringWithEncoding: - [OFLocale encoding]], flags, 0666)) == -1) +# ifdef OF_WINDOWS + if ([OFSystemInfo isWindowsNT]) + handle = _wopen(path.UTF16String, flags, + _S_IREAD | _S_IWRITE); + else +# endif +# ifdef HAVE_OPEN64 + handle = open64( + [path cStringWithEncoding: [OFLocale encoding]], + flags, 0666); # else - if ((handle = open([path cStringWithEncoding: - [OFLocale encoding]], flags, 0666)) == -1) + handle = open( + [path cStringWithEncoding: [OFLocale encoding]], + flags, 0666); # endif + + if (handle == -1) @throw [OFOpenItemFailedException exceptionWithPath: path mode: mode errNo: errno]; #else Index: src/OFFileManager.h ================================================================== --- src/OFFileManager.h +++ src/OFFileManager.h @@ -140,19 +140,19 @@ * via @ref OFDictionary#filePOSIXGID. */ extern const of_file_attribute_key_t of_file_attribute_key_posix_gid; /*! - * @brief The owner of the file as an @ref OFString. + * @brief The owner of the file as an OFString. * * For convenience, a category on @ref OFDictionary is provided to access this * via @ref OFDictionary#fileOwner. */ extern const of_file_attribute_key_t of_file_attribute_key_owner; /*! - * @brief The group of the file as an @ref OFString. + * @brief The group of the file as an OFString. * * For convenience, a category on @ref OFDictionary is provided to access this * via @ref OFDictionary#fileGroup. */ extern const of_file_attribute_key_t of_file_attribute_key_group; @@ -188,11 +188,11 @@ * via @ref OFDictionary#fileCreationDate. */ extern const of_file_attribute_key_t of_file_attribute_key_creation_date; /*! - * @brief The destination of a symbolic link as an @ref OFString. + * @brief The destination of a symbolic link as an OFString. * * For convenience, a category on @ref OFDictionary is provided to access this * via @ref OFDictionary#fileSymbolicLinkDestination. */ extern const of_file_attribute_key_t Index: src/OFFileManager.m ================================================================== --- src/OFFileManager.m +++ src/OFFileManager.m @@ -162,16 +162,28 @@ #ifdef OF_HAVE_FILES - (OFString *)currentDirectoryPath { # if defined(OF_WINDOWS) OFString *ret; - wchar_t *buffer = _wgetcwd(NULL, 0); - @try { - ret = [OFString stringWithUTF16String: buffer]; - } @finally { - free(buffer); + if ([OFSystemInfo isWindowsNT]) { + wchar_t *buffer = _wgetcwd(NULL, 0); + + @try { + ret = [OFString stringWithUTF16String: buffer]; + } @finally { + free(buffer); + } + } else { + char *buffer = _getcwd(NULL, 0); + + @try { + ret = [OFString stringWithCString: buffer + encoding: [OFLocale encoding]]; + } @finally { + free(buffer); + } } return ret; # elif defined(OF_AMIGAOS) char buffer[512]; @@ -480,16 +492,11 @@ - (void)changeCurrentDirectoryPath: (OFString *)path { if (path == nil) @throw [OFInvalidArgumentException exception]; -# if defined(OF_WINDOWS) - if (_wchdir(path.UTF16String) != 0) - @throw [OFChangeCurrentDirectoryPathFailedException - exceptionWithPath: path - errNo: errno]; -# elif defined(OF_AMIGAOS) +# ifdef OF_AMIGAOS BPTR lock, oldLock; if ((lock = Lock([path cStringWithEncoding: [OFLocale encoding]], SHARED_LOCK)) == 0) { int errNo; @@ -519,11 +526,21 @@ else UnLock(oldLock); dirChanged = true; # else - if (chdir([path cStringWithEncoding: [OFLocale encoding]]) != 0) + int status; + +# ifdef OF_WINDOWS + if ([OFSystemInfo isWindowsNT]) + status = _wchdir(path.UTF16String); + else +# endif + status = chdir( + [path cStringWithEncoding: [OFLocale encoding]]); + + if (status != 0) @throw [OFChangeCurrentDirectoryPathFailedException exceptionWithPath: path errNo: errno]; # endif } Index: src/OFFileURLHandler.m ================================================================== --- src/OFFileURLHandler.m +++ src/OFFileURLHandler.m @@ -40,10 +40,11 @@ #import "OFDate.h" #import "OFFile.h" #import "OFFileManager.h" #import "OFLocale.h" #import "OFNumber.h" +#import "OFSystemInfo.h" #import "OFURL.h" #ifdef OF_HAVE_THREADS # import "OFMutex.h" #endif @@ -107,10 +108,12 @@ static OFMutex *readdirMutex; #endif #ifdef OF_WINDOWS static WINAPI BOOLEAN (*func_CreateSymbolicLinkW)(LPCWSTR, LPCWSTR, DWORD); +static WINAPI BOOLEAN (*func_CreateHardLinkW)(LPCWSTR, LPCWSTR, + LPSECURITY_ATTRIBUTES); #endif #ifdef OF_WINDOWS static of_time_interval_t filetimeToTimeInterval(const FILETIME *filetime) @@ -146,13 +149,21 @@ static int of_stat(OFString *path, of_stat_t *buffer) { #if defined(OF_WINDOWS) WIN32_FILE_ATTRIBUTE_DATA data; + bool success; - if (!GetFileAttributesExW(path.UTF16String, GetFileExInfoStandard, - &data)) { + if ([OFSystemInfo isWindowsNT]) + success = GetFileAttributesExW(path.UTF16String, + GetFileExInfoStandard, &data); + else + success = GetFileAttributesExA( + [path cStringWithEncoding: [OFLocale encoding]], + GetFileExInfoStandard, &data); + + if (!success) { setErrno(); return -1; } buffer->st_size = (uint64_t)data.nFileSizeHigh << 32 | @@ -159,10 +170,14 @@ data.nFileSizeLow; if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) buffer->st_mode = S_IFDIR; else if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + /* + * No need to use A functions in this branch: This is only + * available on NTFS (and hence Windows NT) anyway. + */ WIN32_FIND_DATAW findData; HANDLE findHandle; if ((findHandle = FindFirstFileW(path.UTF16String, &findData)) == INVALID_HANDLE_VALUE) { @@ -496,14 +511,19 @@ #if !defined(HAVE_READDIR_R) && !defined(OF_WINDOWS) && defined(OF_HAVE_THREADS) readdirMutex = [[OFMutex alloc] init]; #endif #ifdef OF_WINDOWS - if ((module = LoadLibrary("kernel32.dll")) != NULL) + if ((module = LoadLibrary("kernel32.dll")) != NULL) { func_CreateSymbolicLinkW = (WINAPI BOOLEAN (*)(LPCWSTR, LPCWSTR, DWORD)) GetProcAddress(module, "CreateSymbolicLinkW"); + func_CreateHardLinkW = + (WINAPI BOOLEAN (*)(LPCWSTR, LPCWSTR, + LPSECURITY_ATTRIBUTES)) + GetProcAddress(module, "CreateHardLinkW"); + } #endif /* * Make sure OFFile is initialized. * On some systems, this is needed to initialize the file system driver. @@ -583,16 +603,21 @@ attributes: (of_file_attributes_t)attributes { #ifdef OF_FILE_MANAGER_SUPPORTS_PERMISSIONS uint16_t mode = permissions.uInt16Value & 0777; OFString *path = URL.fileSystemRepresentation; + int status; -# ifndef OF_WINDOWS - if (chmod([path cStringWithEncoding: [OFLocale encoding]], mode) != 0) -# else - if (_wchmod(path.UTF16String, mode) != 0) +# ifdef OF_WINDOWS + if ([OFSystemInfo isWindowsNT]) + status = _wchmod(path.UTF16String, mode); + else # endif + status = chmod( + [path cStringWithEncoding: [OFLocale encoding]], mode); + + if (status != 0) @throw [OFSetItemAttributesFailedException exceptionWithURL: URL attributes: attributes failedAttribute: of_file_attribute_key_posix_permissions errNo: errno]; @@ -771,11 +796,19 @@ @throw [OFInvalidArgumentException exception]; path = URL.fileSystemRepresentation; #if defined(OF_WINDOWS) - if (_wmkdir(path.UTF16String) != 0) + int status; + + if ([OFSystemInfo isWindowsNT]) + status = _wmkdir(path.UTF16String); + else + status = _mkdir( + [path cStringWithEncoding: [OFLocale encoding]]); + + if (status != 0) @throw [OFCreateDirectoryFailedException exceptionWithURL: URL errNo: errno]; #elif defined(OF_AMIGAOS) BPTR lock; @@ -837,49 +870,98 @@ path = URL.fileSystemRepresentation; #if defined(OF_WINDOWS) HANDLE handle; - WIN32_FIND_DATAW fd; path = [path stringByAppendingString: @"\\*"]; - if ((handle = FindFirstFileW(path.UTF16String, - &fd)) == INVALID_HANDLE_VALUE) { - int errNo = 0; - - if (GetLastError() == ERROR_FILE_NOT_FOUND) - errNo = ENOENT; - - @throw [OFOpenItemFailedException exceptionWithURL: URL - mode: nil - errNo: errNo]; - } - - @try { - do { - OFString *file; - - if (!wcscmp(fd.cFileName, L".") || - !wcscmp(fd.cFileName, L"..")) - continue; - - file = [[OFString alloc] - initWithUTF16String: fd.cFileName]; - @try { - [files addObject: file]; - } @finally { - [file release]; - } - } while (FindNextFileW(handle, &fd)); - - if (GetLastError() != ERROR_NO_MORE_FILES) - @throw [OFReadFailedException exceptionWithObject: self - requestedLength: 0 - errNo: EIO]; - } @finally { - FindClose(handle); + if ([OFSystemInfo isWindowsNT]) { + WIN32_FIND_DATAW fd; + + if ((handle = FindFirstFileW(path.UTF16String, + &fd)) == INVALID_HANDLE_VALUE) { + int errNo = 0; + + if (GetLastError() == ERROR_FILE_NOT_FOUND) + errNo = ENOENT; + + @throw [OFOpenItemFailedException + exceptionWithURL: URL + mode: nil + errNo: errNo]; + } + + @try { + do { + OFString *file; + + if (wcscmp(fd.cFileName, L".") == 0 || + wcscmp(fd.cFileName, L"..") == 0) + continue; + + file = [[OFString alloc] + initWithUTF16String: fd.cFileName]; + @try { + [files addObject: file]; + } @finally { + [file release]; + } + } while (FindNextFileW(handle, &fd)); + + if (GetLastError() != ERROR_NO_MORE_FILES) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: 0 + errNo: EIO]; + } @finally { + FindClose(handle); + } + } else { + of_string_encoding_t encoding = [OFLocale encoding]; + WIN32_FIND_DATA fd; + + if ((handle = FindFirstFileA( + [path cStringWithEncoding: encoding], &fd)) == + INVALID_HANDLE_VALUE) { + int errNo = 0; + + if (GetLastError() == ERROR_FILE_NOT_FOUND) + errNo = ENOENT; + + @throw [OFOpenItemFailedException + exceptionWithURL: URL + mode: nil + errNo: errNo]; + } + + @try { + do { + OFString *file; + + if (strcmp(fd.cFileName, ".") == 0 || + strcmp(fd.cFileName, "..") == 0) + continue; + + file = [[OFString alloc] + initWithCString: fd.cFileName + encoding: encoding]; + @try { + [files addObject: file]; + } @finally { + [file release]; + } + } while (FindNextFileA(handle, &fd)); + + if (GetLastError() != ERROR_NO_MORE_FILES) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: 0 + errNo: EIO]; + } @finally { + FindClose(handle); + } } #elif defined(OF_AMIGAOS) of_string_encoding_t encoding = [OFLocale encoding]; BPTR lock; @@ -1085,25 +1167,36 @@ objc_autoreleasePoolPop(pool2); } #ifndef OF_AMIGAOS -# ifndef OF_WINDOWS - if (rmdir([path cStringWithEncoding: [OFLocale encoding]]) != 0) -# else - if (_wrmdir(path.UTF16String) != 0) + int status; + +# ifdef OF_WINDOWS + if ([OFSystemInfo isWindowsNT]) + status = _wrmdir(path.UTF16String); + else # endif + status = rmdir( + [path cStringWithEncoding: [OFLocale encoding]]); + + if (status != 0) @throw [OFRemoveItemFailedException exceptionWithURL: URL errNo: errno]; } else { -# ifndef OF_WINDOWS - if (unlink([path cStringWithEncoding: - [OFLocale encoding]]) != 0) -# else - if (_wunlink(path.UTF16String) != 0) + int status; + +# ifdef OF_WINDOWS + if ([OFSystemInfo isWindowsNT]) + status = _wunlink(path.UTF16String); + else # endif + status = unlink( + [path cStringWithEncoding: [OFLocale encoding]]); + + if (status != 0) @throw [OFRemoveItemFailedException exceptionWithURL: URL errNo: errno]; #endif } @@ -1164,11 +1257,15 @@ @throw [OFLinkFailedException exceptionWithSourceURL: source destinationURL: destination errNo: errno]; # else - if (!CreateHardLinkW(destinationPath.UTF16String, + if (func_CreateHardLinkW == NULL) + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: self]; + + if (!func_CreateHardLinkW(destinationPath.UTF16String, sourcePath.UTF16String, NULL)) @throw [OFLinkFailedException exceptionWithSourceURL: source destinationURL: destination errNo: 0]; @@ -1233,18 +1330,11 @@ destinationURL: destination errNo: EEXIST]; pool = objc_autoreleasePoolPush(); -#if defined(OF_WINDOWS) - if (_wrename(source.fileSystemRepresentation.UTF16String, - destination.fileSystemRepresentation.UTF16String) != 0) - @throw [OFMoveItemFailedException - exceptionWithSourceURL: source - destinationURL: destination - errNo: errno]; -#elif defined(OF_AMIGAOS) +#ifdef OF_AMIGAOS of_string_encoding_t encoding = [OFLocale encoding]; if (!Rename([source.fileSystemRepresentation cStringWithEncoding: encoding], [destination.fileSystemRepresentation @@ -1277,16 +1367,29 @@ exceptionWithSourceURL: source destinationURL: destination errNo: errNo]; } #else - of_string_encoding_t encoding = [OFLocale encoding]; + int status; + +# ifdef OF_WINDOWS + if ([OFSystemInfo isWindowsNT]) + status = _wrename(source.fileSystemRepresentation.UTF16String, + destination.fileSystemRepresentation.UTF16String); + else { +# endif + of_string_encoding_t encoding = [OFLocale encoding]; + + status = rename([source.fileSystemRepresentation + cStringWithEncoding: encoding], + [destination.fileSystemRepresentation + cStringWithEncoding: encoding]); +# ifdef OF_WINDOWS + } +# endif - if (rename([source.fileSystemRepresentation - cStringWithEncoding: encoding], - [destination.fileSystemRepresentation - cStringWithEncoding: encoding]) != 0) + if (status != 0) @throw [OFMoveItemFailedException exceptionWithSourceURL: source destinationURL: destination errNo: errno]; #endif Index: src/OFHTTPClient.h ================================================================== --- src/OFHTTPClient.h +++ src/OFHTTPClient.h @@ -145,11 +145,11 @@ { #ifdef OF_HTTPCLIENT_M @public #endif OFObject *_Nullable _delegate; - bool _insecureRedirectsAllowed, _inProgress; + bool _allowsInsecureRedirects, _inProgress; OFTCPSocket *_Nullable _socket; OFURL *_Nullable _lastURL; bool _lastWasHEAD; OFHTTPResponse *_Nullable _lastResponse; } @@ -159,13 +159,13 @@ */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) OFObject *delegate; /*! - * @brief Whether redirects from HTTPS to HTTP will be allowed. + * @brief Whether the HTTP client allows redirects from HTTPS to HTTP. */ -@property (nonatomic) bool insecureRedirectsAllowed; +@property (nonatomic) bool allowsInsecureRedirects; /*! * @brief Creates a new OFHTTPClient. * * @return A new, autoreleased OFHTTPClient Index: src/OFHTTPClient.m ================================================================== --- src/OFHTTPClient.m +++ src/OFHTTPClient.m @@ -74,10 +74,11 @@ @interface OFHTTPClientRequestBodyStream: OFStream { OFHTTPClientRequestHandler *_handler; OFTCPSocket *_socket; + bool _chunked; uintmax_t _toWrite; bool _atEndOfStream; } - (instancetype)initWithHandler: (OFHTTPClientRequestHandler *)handler @@ -85,12 +86,13 @@ @end @interface OFHTTPClientResponse: OFHTTPResponse { OFTCPSocket *_socket; - bool _hasContentLength, _chunked, _keepAlive, _atEndOfStream; - uintmax_t _toRead; + bool _hasContentLength, _chunked, _keepAlive; + bool _atEndOfStream, _setAtEndOfStream; + intmax_t _toRead; } @property (nonatomic, setter=of_setKeepAlive:) bool of_keepAlive; - (instancetype)initWithSocket: (OFTCPSocket *)sock; @@ -116,10 +118,11 @@ OFURL *URL = request.URL; OFString *path; OFString *user = URL.user, *password = URL.password; OFMutableString *requestString; OFMutableDictionary OF_GENERIC(OFString *, OFString *) *headers; + bool hasContentLength, chunked; OFEnumerator OF_GENERIC(OFString *) *keyEnumerator, *objectEnumerator; OFString *key, *object; if (URL.path != nil) path = URL.URLEncodedPath; @@ -183,11 +186,15 @@ request.protocolVersion.minor == 0 && [headers objectForKey: @"Connection"] == nil) [headers setObject: @"keep-alive" forKey: @"Connection"]; - if ([headers objectForKey: @"Content-Length"] != nil && + hasContentLength = ([headers objectForKey: @"Content-Length"] != nil); + chunked = [[headers objectForKey: @"Transfer-Encoding"] + isEqual: @"chunked"]; + + if ((hasContentLength || chunked) && [headers objectForKey: @"Content-Type"] == nil) [headers setObject: @"application/x-www-form-" @"urlencoded; charset=UTF-8" forKey: @"Content-Type"]; @@ -306,12 +313,11 @@ response.headers = _serverHeaders; connectionHeader = [_serverHeaders objectForKey: @"Connection"]; if ([_version isEqual: @"1.1"]) { if (connectionHeader != nil) - keepAlive = ([connectionHeader caseInsensitiveCompare: - @"close"] != OF_ORDERED_SAME); + keepAlive = [connectionHeader isEqual: @"close"]; else keepAlive = true; } else { if (connectionHeader != nil) keepAlive = ([connectionHeader caseInsensitiveCompare: @@ -328,31 +334,42 @@ _client->_lastWasHEAD = (_request.method == OF_HTTP_REQUEST_METHOD_HEAD); _client->_lastResponse = [response retain]; } - /* FIXME: Case-insensitive check of redirect's scheme */ if (_redirects > 0 && (_status == 301 || _status == 302 || _status == 303 || _status == 307) && - (location = [_serverHeaders objectForKey: @"Location"]) != nil && - (_client->_insecureRedirectsAllowed || - [URL.scheme isEqual: @"http"] || - [location hasPrefix: @"https://"])) { + (location = [_serverHeaders objectForKey: @"Location"]) != nil) { + bool follow = true; OFURL *newURL; - bool follow; + OFString *newURLScheme; newURL = [OFURL URLWithString: location relativeToURL: URL]; + newURLScheme = newURL.scheme; - if ([_client->_delegate respondsToSelector: @selector(client: - shouldFollowRedirect:statusCode:request:response:)]) + if ([newURLScheme caseInsensitiveCompare: @"http"] != + OF_ORDERED_SAME && + [newURLScheme caseInsensitiveCompare: @"https"] != + OF_ORDERED_SAME) + follow = false; + + if (!_client->_allowsInsecureRedirects && + [URL.scheme caseInsensitiveCompare: @"https"] == + OF_ORDERED_SAME && + [newURLScheme caseInsensitiveCompare: @"http"] == + OF_ORDERED_SAME) + follow = false; + + if (follow && [_client->_delegate respondsToSelector: @selector( + client:shouldFollowRedirect:statusCode:request:response:)]) follow = [_client->_delegate client: _client shouldFollowRedirect: newURL statusCode: _status request: _request response: response]; - else + else if (follow) follow = defaultShouldFollow(_request.method, _status); if (follow) { OFDictionary OF_GENERIC(OFString *, OFString *) *headers = _request.headers; @@ -548,10 +565,13 @@ didWriteString: (OFString *)string encoding: (of_string_encoding_t)encoding bytesWritten: (size_t)bytesWritten exception: (id)exception { + OFDictionary OF_GENERIC(OFString *, OFString *) *headers; + bool chunked; + if (exception != nil) { if ([exception isKindOfClass: [OFWriteFailedException class]] && ([exception errNo] == ECONNRESET || [exception errNo] == EPIPE)) { /* In case a keep-alive connection timed out */ @@ -563,11 +583,15 @@ return nil; } _firstLine = true; - if ([_request.headers objectForKey: @"Content-Length"] != nil) { + headers = _request.headers; + chunked = [[headers objectForKey: @"Transfer-Encoding"] + isEqual: @"chunked"]; + + if (chunked || [headers objectForKey: @"Content-Length"] != nil) { stream.delegate = nil; OFStream *requestBody = [[[OFHTTPClientRequestBodyStream alloc] initWithHandler: self socket: (OFTCPSocket *)stream] autorelease]; @@ -668,11 +692,12 @@ uint16_t port; OFNumber *URLPort; [_client close]; - if ([URL.scheme isEqual: @"https"]) { + if ([URL.scheme caseInsensitiveCompare: @"https"] == + OF_ORDERED_SAME) { if (of_tls_socket_class == Nil) @throw [OFUnsupportedProtocolException exceptionWithURL: URL]; sock = [[[of_tls_socket_class alloc] init] autorelease]; @@ -702,28 +727,32 @@ self = [super init]; @try { OFDictionary OF_GENERIC(OFString *, OFString *) *headers; intmax_t contentLength; - OFString *contentLengthString; + OFString *transferEncoding, *contentLengthString; _handler = [handler retain]; _socket = [sock retain]; headers = _handler->_request.headers; + + transferEncoding = [headers objectForKey: @"Transfer-Encoding"]; + _chunked = [transferEncoding isEqual: @"chunked"]; contentLengthString = [headers objectForKey: @"Content-Length"]; - if (contentLengthString == nil) - @throw [OFInvalidArgumentException exception]; - - contentLength = contentLengthString.decimalValue; - if (contentLength < 0) - @throw [OFOutOfRangeException exception]; - - _toWrite = contentLength; - - if ([headers objectForKey: @"Transfer-Encoding"] != nil) + if (contentLengthString != nil) { + if (_chunked || contentLengthString.length == 0) + @throw [OFInvalidArgumentException + exception]; + + contentLength = contentLengthString.decimalValue; + if (contentLength < 0) + @throw [OFOutOfRangeException exception]; + + _toWrite = contentLength; + } else if (!_chunked) @throw [OFInvalidArgumentException exception]; } @catch (id e) { [self release]; @throw e; } @@ -748,30 +777,44 @@ size_t ret; if (_socket == nil) @throw [OFNotOpenException exceptionWithObject: self]; + /* + * We must not send a chunk of size 0, as that would end the body. We + * always ignore writing 0 bytes to still allow writing 0 bytes after + * the end of stream. + */ + if (length == 0) + return 0; + if (_atEndOfStream) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: requestedLength bytesWritten: 0 errNo: 0]; - if (length > _toWrite) + if (_chunked) + [_socket writeFormat: @"%zX\r\n", length]; + else if (length > _toWrite) length = (size_t)_toWrite; ret = [_socket writeBuffer: buffer length: length]; + if (_chunked) + [_socket writeString: @"\r\n"]; if (ret > length) @throw [OFOutOfRangeException exception]; - _toWrite -= ret; + if (!_chunked) { + _toWrite -= ret; - if (_toWrite == 0) - _atEndOfStream = true; + if (_toWrite == 0) + _atEndOfStream = true; + } if (requestedLength > length) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: requestedLength @@ -789,11 +832,13 @@ - (void)close { if (_socket == nil) @throw [OFNotOpenException exceptionWithObject: self]; - if (_toWrite > 0) + if (_chunked) + [_socket writeString: @"0\r\n\r\n"]; + else if (_toWrite > 0) @throw [OFTruncatedDataException exception]; _socket.delegate = _handler; [_socket asyncReadLine]; @@ -838,20 +883,21 @@ _chunked = [[headers objectForKey: @"Transfer-Encoding"] isEqual: @"chunked"]; contentLength = [headers objectForKey: @"Content-Length"]; if (contentLength != nil) { + if (_chunked || contentLength.length == 0) + @throw [OFInvalidServerReplyException exception]; + _hasContentLength = true; @try { - intmax_t toRead = contentLength.decimalValue; + _toRead = contentLength.decimalValue; - if (toRead < 0) + if (_toRead < 0) @throw [OFInvalidServerReplyException exception]; - - _toRead = toRead; } @catch (OFInvalidFormatException *e) { @throw [OFInvalidServerReplyException exception]; } } } @@ -874,11 +920,11 @@ /* Content-Length */ if (!_chunked) { size_t ret; - if (length > _toRead) + if (length > (uintmax_t)_toRead) length = (size_t)_toRead; ret = [_socket readIntoBuffer: buffer length: length]; @@ -892,67 +938,101 @@ return ret; } /* Chunked */ - if (_toRead > 0) { - if (length > _toRead) + if (_toRead == -2) { + char tmp[2]; + + switch ([_socket readIntoBuffer: tmp + length: 2]) { + case 2: + _toRead++; + if (tmp[1] != '\n') + @throw [OFInvalidServerReplyException + exception]; + case 1: + _toRead++; + if (tmp[0] != '\r') + @throw [OFInvalidServerReplyException + exception]; + } + + if (_setAtEndOfStream && _toRead == 0) + _atEndOfStream = true; + + return 0; + } else if (_toRead == -1) { + char tmp; + + if ([_socket readIntoBuffer: &tmp + length: 1] == 1) { + _toRead++; + if (tmp != '\n') + @throw [OFInvalidServerReplyException + exception]; + } + + if (_setAtEndOfStream && _toRead == 0) + _atEndOfStream = true; + + return 0; + } else if (_toRead > 0) { + if (length > (uintmax_t)_toRead) length = (size_t)_toRead; length = [_socket readIntoBuffer: buffer length: length]; _toRead -= length; if (_toRead == 0) - if ([_socket readLine].length > 0) - @throw [OFInvalidServerReplyException - exception]; + _toRead = -2; return length; } else { void *pool = objc_autoreleasePoolPush(); OFString *line; of_range_t range; @try { - line = [_socket readLine]; + line = [_socket tryReadLine]; } @catch (OFInvalidEncodingException *e) { @throw [OFInvalidServerReplyException exception]; } + + if (line == nil) + return 0; range = [line rangeOfString: @";"]; if (range.location != OF_NOT_FOUND) line = [line substringWithRange: of_range(0, range.location)]; + + if (line.length < 1) { + /* + * We have read the empty string because the socket is + * at end of stream. + */ + if (_socket.atEndOfStream && + range.location == OF_NOT_FOUND) + @throw [OFTruncatedDataException exception]; + else + @throw [OFInvalidServerReplyException + exception]; + } @try { - intmax_t toRead = line.hexadecimalValue; - - if (toRead < 0) + if ((_toRead = line.hexadecimalValue) < 0) @throw [OFOutOfRangeException exception]; - - _toRead = toRead; } @catch (OFInvalidFormatException *e) { @throw [OFInvalidServerReplyException exception]; } if (_toRead == 0) { - _atEndOfStream = true; - - if (_keepAlive) { - @try { - line = [_socket readLine]; - } @catch (OFInvalidEncodingException *e) { - @throw [OFInvalidServerReplyException - exception]; - } - - if (line.length > 0) - @throw [OFInvalidServerReplyException - exception]; - } + _setAtEndOfStream = true; + _toRead = -2; } objc_autoreleasePoolPop(pool); return 0; @@ -1118,11 +1198,11 @@ } @end @implementation OFHTTPClient @synthesize delegate = _delegate; -@synthesize insecureRedirectsAllowed = _insecureRedirectsAllowed; +@synthesize allowsInsecureRedirects = _allowsInsecureRedirects; + (instancetype)client { return [[[self alloc] init] autorelease]; } @@ -1168,11 +1248,12 @@ { void *pool = objc_autoreleasePoolPush(); OFURL *URL = request.URL; OFString *scheme = URL.scheme; - if (![scheme isEqual: @"http"] && ![scheme isEqual: @"https"]) + if ([scheme caseInsensitiveCompare: @"http"] != OF_ORDERED_SAME && + [scheme caseInsensitiveCompare: @"https"] != OF_ORDERED_SAME) @throw [OFUnsupportedProtocolException exceptionWithURL: URL]; if (_inProgress) /* TODO: Find a better exception */ @throw [OFAlreadyConnectedException exception]; Index: src/OFHTTPCookieManager.m ================================================================== --- src/OFHTTPCookieManager.m +++ src/OFHTTPCookieManager.m @@ -63,11 +63,12 @@ size_t i; if (![cookie.path hasPrefix: @"/"]) cookie.path = @"/"; - if (cookie.secure && ![URL.scheme isEqual: @"https"]) { + if (cookie.secure && + [URL.scheme caseInsensitiveCompare: @"https"] != OF_ORDERED_SAME) { objc_autoreleasePoolPop(pool); return; } cookieDomain = cookie.domain.lowercaseString; @@ -123,11 +124,12 @@ expires = cookie.expires; if (expires != nil && expires.timeIntervalSinceNow <= 0) continue; - if (cookie.secure && ![URL.scheme isEqual: @"https"]) + if (cookie.secure && [URL.scheme caseInsensitiveCompare: + @"https"] != OF_ORDERED_SAME) continue; pool = objc_autoreleasePoolPush(); cookieDomain = cookie.domain.lowercaseString; Index: src/OFHTTPRequest.h ================================================================== --- src/OFHTTPRequest.h +++ src/OFHTTPRequest.h @@ -55,16 +55,18 @@ * @struct of_http_request_protocol_version_t \ * OFHTTPRequest.h ObjFW/OFHTTPRequest.h * * @brief The HTTP version of the HTTP request. */ -typedef struct OF_BOXABLE { +struct OF_BOXABLE of_http_request_protocol_version_t { /*! The major of the HTTP version */ uint8_t major; /*! The minor of the HTTP version */ uint8_t minor; -} of_http_request_protocol_version_t; +}; +typedef struct of_http_request_protocol_version_t + of_http_request_protocol_version_t; /*! * @class OFHTTPRequest OFHTTPRequest.h ObjFW/OFHTTPRequest.h * * @brief A class for storing HTTP requests. @@ -154,11 +156,11 @@ * * @param string The string for which the request method should be returned * @return The request method for the specified string */ extern of_http_request_method_t of_http_request_method_from_string( - const char *string); + OFString *string); #ifdef __cplusplus } #endif OF_ASSUME_NONNULL_END Index: src/OFHTTPRequest.m ================================================================== --- src/OFHTTPRequest.m +++ src/OFHTTPRequest.m @@ -24,10 +24,11 @@ #import "OFURL.h" #import "OFDictionary.h" #import "OFData.h" #import "OFArray.h" +#import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfRangeException.h" #import "OFUnsupportedVersionException.h" const char * @@ -54,30 +55,30 @@ return NULL; } of_http_request_method_t -of_http_request_method_from_string(const char *string) +of_http_request_method_from_string(OFString *string) { - if (strcmp(string, "OPTIONS") == 0) + if ([string isEqual: @"OPTIONS"]) return OF_HTTP_REQUEST_METHOD_OPTIONS; - if (strcmp(string, "GET") == 0) + if ([string isEqual: @"GET"]) return OF_HTTP_REQUEST_METHOD_GET; - if (strcmp(string, "HEAD") == 0) + if ([string isEqual: @"HEAD"]) return OF_HTTP_REQUEST_METHOD_HEAD; - if (strcmp(string, "POST") == 0) + if ([string isEqual: @"POST"]) return OF_HTTP_REQUEST_METHOD_POST; - if (strcmp(string, "PUT") == 0) + if ([string isEqual: @"PUT"]) return OF_HTTP_REQUEST_METHOD_PUT; - if (strcmp(string, "DELETE") == 0) + if ([string isEqual: @"DELETE"]) return OF_HTTP_REQUEST_METHOD_DELETE; - if (strcmp(string, "TRACE") == 0) + if ([string isEqual: @"TRACE"]) return OF_HTTP_REQUEST_METHOD_TRACE; - if (strcmp(string, "CONNECT") == 0) + if ([string isEqual: @"CONNECT"]) return OF_HTTP_REQUEST_METHOD_CONNECT; - @throw [OFInvalidFormatException exception]; + @throw [OFInvalidArgumentException exception]; } @implementation OFHTTPRequest @synthesize URL = _URL, method = _method, headers = _headers; Index: src/OFHTTPResponse.h ================================================================== --- src/OFHTTPResponse.h +++ src/OFHTTPResponse.h @@ -68,7 +68,15 @@ * * @return The reply as a string */ - (OFString *)stringWithEncoding: (of_string_encoding_t)encoding; @end + +#ifdef __cplusplus +extern "C" { +#endif +extern OFString *_Nonnull of_http_status_code_to_string(short code); +#ifdef __cplusplus +} +#endif OF_ASSUME_NONNULL_END Index: src/OFHTTPResponse.m ================================================================== --- src/OFHTTPResponse.m +++ src/OFHTTPResponse.m @@ -21,15 +21,104 @@ #import "OFString.h" #import "OFDictionary.h" #import "OFArray.h" #import "OFData.h" -#import "OFInvalidEncodingException.h" +#import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfRangeException.h" #import "OFTruncatedDataException.h" #import "OFUnsupportedVersionException.h" + +OFString * +of_http_status_code_to_string(short code) +{ + switch (code) { + case 100: + return @"Continue"; + case 101: + return @"Switching Protocols"; + case 200: + return @"OK"; + case 201: + return @"Created"; + case 202: + return @"Accepted"; + case 203: + return @"Non-Authoritative Information"; + case 204: + return @"No Content"; + case 205: + return @"Reset Content"; + case 206: + return @"Partial Content"; + case 300: + return @"Multiple Choices"; + case 301: + return @"Moved Permanently"; + case 302: + return @"Found"; + case 303: + return @"See Other"; + case 304: + return @"Not Modified"; + case 305: + return @"Use Proxy"; + case 307: + return @"Temporary Redirect"; + case 400: + return @"Bad Request"; + case 401: + return @"Unauthorized"; + case 402: + return @"Payment Required"; + case 403: + return @"Forbidden"; + case 404: + return @"Not Found"; + case 405: + return @"Method Not Allowed"; + case 406: + return @"Not Acceptable"; + case 407: + return @"Proxy Authentication Required"; + case 408: + return @"Request Timeout"; + case 409: + return @"Conflict"; + case 410: + return @"Gone"; + case 411: + return @"Length Required"; + case 412: + return @"Precondition Failed"; + case 413: + return @"Request Entity Too Large"; + case 414: + return @"Request-URI Too Long"; + case 415: + return @"Unsupported Media Type"; + case 416: + return @"Requested Range Not Satisfiable"; + case 417: + return @"Expectation Failed"; + case 500: + return @"Internal Server Error"; + case 501: + return @"Not Implemented"; + case 502: + return @"Bad Gateway"; + case 503: + return @"Service Unavailable"; + case 504: + return @"Gateway Timeout"; + case 505: + return @"HTTP Version Not Supported"; + default: + return @"(unknown)"; + } +} static of_string_encoding_t encodingForContentType(OFString *contentType) { const char *UTF8String = contentType.UTF8String; @@ -127,11 +216,11 @@ charset = value; } @try { ret = of_string_parse_encoding(charset); - } @catch (OFInvalidEncodingException *e) { + } @catch (OFInvalidArgumentException *e) { ret = OF_STRING_ENCODING_AUTODETECT; } return ret; } Index: src/OFHTTPServer.m ================================================================== --- src/OFHTTPServer.m +++ src/OFHTTPServer.m @@ -34,14 +34,16 @@ #import "OFTimer.h" #import "OFURL.h" #import "OFAlreadyConnectedException.h" #import "OFInvalidArgumentException.h" +#import "OFInvalidEncodingException.h" #import "OFInvalidFormatException.h" #import "OFNotOpenException.h" #import "OFOutOfMemoryException.h" #import "OFOutOfRangeException.h" +#import "OFTruncatedDataException.h" #import "OFUnsupportedProtocolException.h" #import "OFWriteFailedException.h" #import "socket_helpers.h" @@ -55,25 +57,25 @@ @interface OFHTTPServer () @end @interface OFHTTPServerResponse: OFHTTPResponse { - OFTCPSocket *_socket; + OFStreamSocket *_socket; OFHTTPServer *_server; OFHTTPRequest *_request; bool _chunked, _headersSent; } -- (instancetype)initWithSocket: (OFTCPSocket *)sock +- (instancetype)initWithSocket: (OFStreamSocket *)sock server: (OFHTTPServer *)server request: (OFHTTPRequest *)request; @end @interface OFHTTPServerConnection: OFObject { @public - OFTCPSocket *_socket; + OFStreamSocket *_socket; OFHTTPServer *_server; OFTimer *_timer; enum { AWAITING_PROLOG, PARSING_HEADERS, @@ -86,124 +88,37 @@ OFMutableDictionary *_headers; size_t _contentLength; OFStream *_requestBody; } -- (instancetype)initWithSocket: (OFTCPSocket *)sock +- (instancetype)initWithSocket: (OFStreamSocket *)sock server: (OFHTTPServer *)server; - (bool)parseProlog: (OFString *)line; - (bool)parseHeaders: (OFString *)line; - (bool)sendErrorAndClose: (short)statusCode; - (void)createResponse; @end @interface OFHTTPServerRequestBodyStream: OFStream { - OFTCPSocket *_socket; - uintmax_t _toRead; - bool _atEndOfStream; + OFStreamSocket *_socket; + bool _chunked; + intmax_t _toRead; + bool _atEndOfStream, _setAtEndOfStream; } -- (instancetype)initWithSocket: (OFTCPSocket *)sock +- (instancetype)initWithSocket: (OFStreamSocket *)sock + chunked: (bool)chunked contentLength: (uintmax_t)contentLength; @end #ifdef OF_HAVE_THREADS @interface OFHTTPServerThread: OFThread - (void)stop; @end #endif -static const char * -statusCodeToString(short code) -{ - switch (code) { - case 100: - return "Continue"; - case 101: - return "Switching Protocols"; - case 200: - return "OK"; - case 201: - return "Created"; - case 202: - return "Accepted"; - case 203: - return "Non-Authoritative Information"; - case 204: - return "No Content"; - case 205: - return "Reset Content"; - case 206: - return "Partial Content"; - case 300: - return "Multiple Choices"; - case 301: - return "Moved Permanently"; - case 302: - return "Found"; - case 303: - return "See Other"; - case 304: - return "Not Modified"; - case 305: - return "Use Proxy"; - case 307: - return "Temporary Redirect"; - case 400: - return "Bad Request"; - case 401: - return "Unauthorized"; - case 402: - return "Payment Required"; - case 403: - return "Forbidden"; - case 404: - return "Not Found"; - case 405: - return "Method Not Allowed"; - case 406: - return "Not Acceptable"; - case 407: - return "Proxy Authentication Required"; - case 408: - return "Request Timeout"; - case 409: - return "Conflict"; - case 410: - return "Gone"; - case 411: - return "Length Required"; - case 412: - return "Precondition Failed"; - case 413: - return "Request Entity Too Large"; - case 414: - return "Request-URI Too Long"; - case 415: - return "Unsupported Media Type"; - case 416: - return "Requested Range Not Satisfiable"; - case 417: - return "Expectation Failed"; - case 500: - return "Internal Server Error"; - case 501: - return "Not Implemented"; - case 502: - return "Bad Gateway"; - case 503: - return "Service Unavailable"; - case 504: - return "Gateway Timeout"; - case 505: - return "HTTP Version Not Supported"; - default: - return NULL; - } -} - static OF_INLINE OFString * normalizedKey(OFString *key) { char *cString = of_strdup(key.UTF8String); unsigned char *tmp = (unsigned char *)cString; @@ -231,11 +146,11 @@ return [OFString stringWithUTF8StringNoCopy: cString freeWhenDone: true]; } @implementation OFHTTPServerResponse -- (instancetype)initWithSocket: (OFTCPSocket *)sock +- (instancetype)initWithSocket: (OFStreamSocket *)sock server: (OFHTTPServer *)server request: (OFHTTPRequest *)request { self = [super init]; @@ -263,13 +178,13 @@ void *pool = objc_autoreleasePoolPush(); OFMutableDictionary OF_GENERIC(OFString *, OFString *) *headers; OFEnumerator *keyEnumerator, *valueEnumerator; OFString *key, *value; - [_socket writeFormat: @"HTTP/%@ %d %s\r\n", + [_socket writeFormat: @"HTTP/%@ %d %@\r\n", self.protocolVersionString, _statusCode, - statusCodeToString(_statusCode)]; + of_http_status_code_to_string(_statusCode)]; headers = [[_headers mutableCopy] autorelease]; if ([headers objectForKey: @"Date"] == nil) { OFString *date = [[OFDate date] @@ -318,17 +233,16 @@ if (!_chunked) return [_socket writeBuffer: buffer length: length]; pool = objc_autoreleasePoolPush(); - [_socket writeString: [OFString stringWithFormat: @"%zx\r\n", length]]; + [_socket writeString: [OFString stringWithFormat: @"%zX\r\n", length]]; objc_autoreleasePoolPop(pool); [_socket writeBuffer: buffer length: length]; - [_socket writeBuffer: "\r\n" - length: 2]; + [_socket writeString: @"\r\n"]; return length; } - (void)close @@ -339,12 +253,11 @@ @try { if (!_headersSent) [self of_sendHeaders]; if (_chunked) - [_socket writeBuffer: "0\r\n\r\n" - length: 5]; + [_socket writeString: @"0\r\n\r\n"]; } @catch (OFWriteFailedException *e) { id delegate = _server.delegate; if ([delegate respondsToSelector: @selector(server: didReceiveExceptionForResponse:request:exception:)]) @@ -368,11 +281,11 @@ return _socket.fileDescriptorForWriting; } @end @implementation OFHTTPServerConnection -- (instancetype)initWithSocket: (OFTCPSocket *)sock +- (instancetype)initWithSocket: (OFStreamSocket *)sock server: (OFHTTPServer *)server { self = [super init]; @try { @@ -459,12 +372,12 @@ if (pos == OF_NOT_FOUND) return [self sendErrorAndClose: 400]; method = [line substringWithRange: of_range(0, pos)]; @try { - _method = of_http_request_method_from_string(method.UTF8String); - } @catch (OFInvalidFormatException *e) { + _method = of_http_request_method_from_string(method); + } @catch (OFInvalidArgumentException *e) { return [self sendErrorAndClose: 405]; } @try { of_range_t range = of_range(pos + 1, line.length - pos - 10); @@ -492,31 +405,37 @@ { OFString *key, *value, *old; size_t pos; if (line.length == 0) { - OFString *contentLengthString; + bool chunked = [[_headers objectForKey: @"Transfer-Encoding"] + isEqual: @"chunked"]; + OFString *contentLengthString = + [_headers objectForKey: @"Content-Length"]; + intmax_t contentLength = 0; - if ((contentLengthString = - [_headers objectForKey: @"Content-Length"]) != nil) { - intmax_t contentLength; + if (contentLengthString != nil) { + if (chunked || contentLengthString.length == 0) + return [self sendErrorAndClose: 400]; @try { contentLength = contentLengthString.decimalValue; - } @catch (OFInvalidFormatException *e) { return [self sendErrorAndClose: 400]; } if (contentLength < 0) return [self sendErrorAndClose: 400]; + } + if (chunked || contentLengthString != nil) { [_requestBody release]; _requestBody = nil; _requestBody = [[OFHTTPServerRequestBodyStream alloc] initWithSocket: _socket + chunked: chunked contentLength: contentLength]; [_timer invalidate]; [_timer release]; _timer = nil; @@ -582,15 +501,16 @@ - (bool)sendErrorAndClose: (short)statusCode { OFString *date = [[OFDate date] dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"]; - [_socket writeFormat: @"HTTP/1.1 %d %s\r\n" + [_socket writeFormat: @"HTTP/1.1 %d %@\r\n" @"Date: %@\r\n" @"Server: %@\r\n" @"\r\n", - statusCode, statusCodeToString(statusCode), + statusCode, + of_http_status_code_to_string(statusCode), date, _server.name]; return false; } @@ -657,18 +577,23 @@ objc_autoreleasePoolPop(pool); } @end @implementation OFHTTPServerRequestBodyStream -- (instancetype)initWithSocket: (OFTCPSocket *)sock +- (instancetype)initWithSocket: (OFStreamSocket *)sock + chunked: (bool)chunked contentLength: (uintmax_t)contentLength { self = [super init]; @try { _socket = [sock retain]; + _chunked = chunked; _toRead = contentLength; + + if (_chunked && _toRead > 0) + @throw [OFInvalidArgumentException exception]; } @catch (id e) { [self release]; @throw e; } @@ -689,26 +614,127 @@ } - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length { - size_t ret; - - if (_toRead == 0) { - _atEndOfStream = true; - return 0; - } - - if (length > _toRead) - length = (size_t)_toRead; - - ret = [_socket readIntoBuffer: buffer - length: length]; - - _toRead -= ret; - - return ret; + if (_socket == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_atEndOfStream) + return 0; + + if (_socket.atEndOfStream) + @throw [OFTruncatedDataException exception]; + + /* Content-Length */ + if (!_chunked) { + size_t ret; + + if (length > (uintmax_t)_toRead) + length = (size_t)_toRead; + + ret = [_socket readIntoBuffer: buffer + length: length]; + + _toRead -= ret; + + if (_toRead == 0) + _atEndOfStream = true; + + return ret; + } + + /* Chunked */ + if (_toRead == -2) { + char tmp[2]; + + switch ([_socket readIntoBuffer: tmp + length: 2]) { + case 2: + _toRead++; + if (tmp[1] != '\n') + @throw [OFInvalidFormatException exception]; + case 1: + _toRead++; + if (tmp[0] != '\r') + @throw [OFInvalidFormatException exception]; + } + + if (_setAtEndOfStream && _toRead == 0) + _atEndOfStream = true; + + return 0; + } else if (_toRead == -1) { + char tmp; + + if ([_socket readIntoBuffer: &tmp + length: 1] == 1) { + _toRead++; + if (tmp != '\n') + @throw [OFInvalidFormatException exception]; + } + + if (_setAtEndOfStream && _toRead == 0) + _atEndOfStream = true; + + return 0; + } else if (_toRead > 0) { + if (length > (uintmax_t)_toRead) + length = (size_t)_toRead; + + length = [_socket readIntoBuffer: buffer + length: length]; + + _toRead -= length; + + if (_toRead == 0) + _toRead = -2; + + return length; + } else { + void *pool = objc_autoreleasePoolPush(); + OFString *line; + of_range_t range; + + @try { + line = [_socket tryReadLine]; + } @catch (OFInvalidEncodingException *e) { + @throw [OFInvalidFormatException exception]; + } + + if (line == nil) + return 0; + + range = [line rangeOfString: @";"]; + if (range.location != OF_NOT_FOUND) + line = [line substringWithRange: + of_range(0, range.location)]; + + if (line.length < 1) { + /* + * We have read the empty string because the socket is + * at end of stream. + */ + if (_socket.atEndOfStream && + range.location == OF_NOT_FOUND) + @throw [OFTruncatedDataException exception]; + else + @throw [OFInvalidFormatException exception]; + } + + if ((_toRead = line.hexadecimalValue) < 0) + @throw [OFOutOfRangeException exception]; + + if (_toRead == 0) { + _setAtEndOfStream = true; + _toRead = -2; + } + + objc_autoreleasePoolPop(pool); + + return 0; + } } - (bool)hasDataInReadBuffer { return (super.hasDataInReadBuffer || _socket.hasDataInReadBuffer); @@ -948,22 +974,22 @@ [_threadPool release]; _threadPool = nil; #endif } -- (void)of_handleAcceptedSocket: (OFTCPSocket *)acceptedSocket +- (void)of_handleAcceptedSocket: (OFStreamSocket *)acceptedSocket { OFHTTPServerConnection *connection = [[[OFHTTPServerConnection alloc] initWithSocket: acceptedSocket server: self] autorelease]; acceptedSocket.delegate = connection; [acceptedSocket asyncReadLine]; } -- (bool)socket: (OFTCPSocket *)sock - didAcceptSocket: (OFTCPSocket *)acceptedSocket +- (bool)socket: (OFStreamSocket *)sock + didAcceptSocket: (OFStreamSocket *)acceptedSocket exception: (id)exception { if (exception != nil) { if (![_delegate respondsToSelector: @selector(server:didReceiveExceptionOnListeningSocket:)]) ADDED src/OFIPSocketAsyncConnector.h Index: src/OFIPSocketAsyncConnector.h ================================================================== --- src/OFIPSocketAsyncConnector.h +++ src/OFIPSocketAsyncConnector.h @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFDNSResolver.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" + +OF_ASSUME_NONNULL_BEGIN + +@protocol OFIPSocketAsyncConnecting +- (bool)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (void)of_closeSocket; +@end + +@interface OFIPSocketAsyncConnector: OFObject +{ + id _socket; + OFString *_host; + uint16_t _port; + id _Nullable _delegate; + id _Nullable _block; + id _Nullable _exception; + OFData *_Nullable _socketAddresses; + size_t _socketAddressesIndex; +} + +- (instancetype)initWithSocket: (id)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (nullable id)delegate + block: (nullable id)block; +- (void)didConnect; +- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFIPSocketAsyncConnector.m Index: src/OFIPSocketAsyncConnector.m ================================================================== --- src/OFIPSocketAsyncConnector.m +++ src/OFIPSocketAsyncConnector.m @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "OFIPSocketAsyncConnector.h" +#import "OFData.h" +#ifdef OF_HAVE_SCTP +# import "OFSCTPSocket.h" +#endif +#import "OFTCPSocket.h" +#import "OFThread.h" +#import "OFTimer.h" + +#import "OFConnectionFailedException.h" +#import "OFInvalidFormatException.h" + +@implementation OFIPSocketAsyncConnector +- (instancetype)initWithSocket: (id)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (id)delegate + block: (id)block +{ + self = [super init]; + + @try { + _socket = [sock retain]; + _host = [host copy]; + _port = port; + _delegate = [delegate retain]; + _block = [block copy]; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_socket release]; + [_host release]; + [_delegate release]; + [_block release]; + [_exception release]; + [_socketAddresses release]; + + [super dealloc]; +} + +- (void)didConnect +{ + if (_exception == nil) + [_socket setCanBlock: true]; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) { + if ([_socket isKindOfClass: [OFTCPSocket class]]) + ((of_tcp_socket_async_connect_block_t)_block)( + _exception); +# ifdef OF_HAVE_SCTP + else if ([_socket isKindOfClass: [OFSCTPSocket class]]) + ((of_sctp_socket_async_connect_block_t)_block)( + _exception); +# endif + else + OF_ENSURE(0); + } else { +#endif + if ([_delegate respondsToSelector: + @selector(socket:didConnectToHost:port:exception:)]) + [_delegate socket: _socket + didConnectToHost: _host + port: _port + exception: _exception]; +#ifdef OF_HAVE_BLOCKS + } +#endif +} + +- (void)of_socketDidConnect: (id)sock + exception: (id)exception +{ + if (exception != nil) { + /* + * self might be retained only by the pending async requests, + * which we're about to cancel. + */ + [[self retain] autorelease]; + + [sock cancelAsyncRequests]; + [sock of_closeSocket]; + + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [exception retain]; + [self didConnect]; + } else { + /* + * We must not call it before returning, as otherwise + * the new socket would be removed from the queue upon + * return. + */ + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + SEL selector = + @selector(tryNextAddressWithRunLoopMode:); + OFTimer *timer = [OFTimer + timerWithTimeInterval: 0 + target: self + selector: selector + object: runLoop.currentMode + repeats: false]; + [runLoop addTimer: timer + forMode: runLoop.currentMode]; + } + + return; + } + + [self didConnect]; +} + +- (id)of_connectionFailedExceptionForErrNo: (int)errNo +{ + return [OFConnectionFailedException exceptionWithHost: _host + port: _port + socket: _socket + errNo: errNo]; +} + +- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + of_socket_address_t address = *(const of_socket_address_t *) + [_socketAddresses itemAtIndex: _socketAddressesIndex++]; + int errNo; + + of_socket_address_set_port(&address, _port); + + if (![_socket of_createSocketForAddress: &address + errNo: &errNo]) { + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return; + } + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; + } + +#if defined(OF_NINTENDO_3DS) || defined(OF_WII) + /* + * On Wii and 3DS, connect() fails if non-blocking is enabled. + * + * Additionally, on Wii, there is no getsockopt(), so it would not be + * possible to get the error (or success) after connecting anyway. + * + * So for now, connecting is blocking on Wii and 3DS. + * + * FIXME: Use a different thread as a work around. + */ + [_socket setCanBlock: true]; +#else + [_socket setCanBlock: false]; +#endif + + if (![_socket of_connectSocketToAddress: &address + errNo: &errNo]) { +#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) + if (errNo == EINPROGRESS) { + [OFRunLoop of_addAsyncConnectForSocket: _socket + mode: runLoopMode + delegate: self]; + return; + } else { +#endif + [_socket of_closeSocket]; + + if (_socketAddressesIndex >= _socketAddresses.count) { + _exception = [[OFConnectionFailedException + alloc] initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return; + } + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; +#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) + } +#endif + } + +#if defined(OF_NINTENDO_3DS) || defined(OF_WII) + [_socket setCanBlock: false]; +#endif + + [self didConnect]; +} + +- (void)resolver: (OFDNSResolver *)resolver + didResolveHost: (OFString *)host + addresses: (OFData *)addresses + exception: (id)exception +{ + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return; + } + + _socketAddresses = [addresses copy]; + + [self tryNextAddressWithRunLoopMode: + [OFRunLoop currentRunLoop].currentMode]; +} + +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + @try { + of_socket_address_t address = + of_socket_address_parse_ip(_host, _port); + + _socketAddresses = [[OFData alloc] + initWithItems: &address + itemSize: sizeof(address) + count: 1]; + + [self tryNextAddressWithRunLoopMode: runLoopMode]; + return; + } @catch (OFInvalidFormatException *e) { + } + + [[OFThread DNSResolver] + asyncResolveAddressesForHost: _host + addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY + runLoopMode: runLoopMode + delegate: self]; +} +@end ADDED src/OFIPXSocket.h Index: src/OFIPXSocket.h ================================================================== --- src/OFIPXSocket.h +++ src/OFIPXSocket.h @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFDatagramSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +/*! + * @protocol OFIPXSocketDelegate OFIPXSocket.h ObjFW/OFIPXSocket.h + * + * @brief A delegate for OFIPXSocket. + */ +@protocol OFIPXSocketDelegate +@end + +/*! + * @class OFIPXSocket OFIPXSocket.h ObjFW/OFIPXSocket.h + * + * @brief A class which provides methods to create and use IPX sockets. + * + * Addresses are of type @ref of_socket_address_t. You can use + * @ref of_socket_address_ipx to create an address or + * @ref of_socket_address_get_ipx_network to get the IPX network, + * @ref of_socket_address_get_ipx_node to get the IPX node and + * @ref of_socket_address_get_port to get the port (sometimes also called + * socket number). + * + * @warning Even though the OFCopying protocol is implemented, it does *not* + * return an independent copy of the socket, but instead retains it. + * This is so that the socket can be used as a key for a dictionary, + * so context can be associated with a socket. Using a socket in more + * than one thread at the same time is not thread-safe, even if copy + * was called to create one "instance" for every thread! + */ +@interface OFIPXSocket: OFDatagramSocket +{ +#ifndef OF_WINDOWS + uint8_t _packetType; +#endif + OF_RESERVE_IVARS(4) +} + +/*! + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Bind the socket to the specified network, node and port with the + * specified packet type. + * + * @param port The port (sometimes called socket number) to bind to. 0 means to + * pick one and return it. + * @param packetType The packet type to use on the socket + * @return The address on which this socket can be reached + */ +- (of_socket_address_t)bindToPort: (uint16_t)port + packetType: (uint8_t)packetType; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFIPXSocket.m Index: src/OFIPXSocket.m ================================================================== --- src/OFIPXSocket.m +++ src/OFIPXSocket.m @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFIPXSocket.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" + +#import "socket.h" +#import "socket_helpers.h" + +@implementation OFIPXSocket +@dynamic delegate; + +- (of_socket_address_t)bindToPort: (uint16_t)port + packetType: (uint8_t)packetType +{ + const unsigned char zeroNode[IPX_NODE_LEN] = { 0 }; + of_socket_address_t address; + int protocol = 0; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = of_socket_address_ipx(zeroNode, 0, port); + +#ifdef OF_WINDOWS + protocol = NSPROTO_IPX + packetType; +#else + _packetType = address.sockaddr.ipx.sipx_type = packetType; +#endif + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_DGRAM | SOCK_CLOEXEC, protocol)) == INVALID_SOCKET) + @throw [OFBindFailedException + exceptionWithPort: port + packetType: packetType + socket: self + errNo: of_socket_errno()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + if (bind(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: packetType + socket: self + errNo: errNo]; + } + + memset(&address, 0, sizeof(address)); + address.family = OF_SOCKET_ADDRESS_FAMILY_IPX; + address.length = (socklen_t)sizeof(address.sockaddr); + + if (of_getsockname(_socket, &address.sockaddr.sockaddr, + &address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: packetType + socket: self + errNo: errNo]; + } + + if (address.sockaddr.sockaddr.sa_family != AF_IPX) { + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: packetType + socket: self + errNo: EAFNOSUPPORT]; + } + + return address; +} + +#ifndef OF_WINDOWS +- (void)sendBuffer: (const void *)buffer + length: (size_t)length + receiver: (const of_socket_address_t *)receiver +{ + of_socket_address_t fixedReceiver; + + memcpy(&fixedReceiver, receiver, sizeof(fixedReceiver)); + + /* If it's not IPX, no fix-up needed - it will fail anyway. */ + if (fixedReceiver.family == OF_SOCKET_ADDRESS_FAMILY_IPX) + fixedReceiver.sockaddr.ipx.sipx_type = _packetType; + + [super sendBuffer: buffer + length: length + receiver: &fixedReceiver]; +} +#endif +@end Index: src/OFInvertedCharacterSet.h ================================================================== --- src/OFInvertedCharacterSet.h +++ src/OFInvertedCharacterSet.h @@ -23,10 +23,9 @@ { OFCharacterSet *_characterSet; bool (*_characterIsMember)(id, SEL, of_unichar_t); } -- (instancetype)of_initWithCharacterSet: (OFCharacterSet *)characterSet - OF_METHOD_FAMILY(init); +- (instancetype)initWithCharacterSet: (OFCharacterSet *)characterSet; @end OF_ASSUME_NONNULL_END Index: src/OFInvertedCharacterSet.m ================================================================== --- src/OFInvertedCharacterSet.m +++ src/OFInvertedCharacterSet.m @@ -26,17 +26,23 @@ - (instancetype)init { OF_INVALID_INIT_METHOD } -- (instancetype)of_initWithCharacterSet: (OFCharacterSet *)characterSet +- (instancetype)initWithCharacterSet: (OFCharacterSet *)characterSet { self = [super init]; - _characterSet = [characterSet retain]; - _characterIsMember = (bool (*)(id, SEL, of_unichar_t)) - [_characterSet methodForSelector: @selector(characterIsMember:)]; + @try { + _characterSet = [characterSet retain]; + _characterIsMember = (bool (*)(id, SEL, of_unichar_t)) + [_characterSet methodForSelector: + @selector(characterIsMember:)]; + } @catch (id e) { + [self release]; + @throw e; + } return self; } - (void)dealloc Index: src/OFLocale.h ================================================================== --- src/OFLocale.h +++ src/OFLocale.h @@ -181,16 +181,16 @@ * @note Generally, you want to use @ref OF_LOCALIZED instead, which also takes * care of the `nil` sentinel automatically. * * @param ID The ID for the localized string * @param fallback The fallback to use in case the localized string cannot be - * looked up or is missing + * looked up or is missing. This can also be an array and use + * plural scripting, just like with the JSON language files. * @return The localized string */ - (OFString *)localizedStringForID: (OFConstantString *)ID - fallback: (OFConstantString *)fallback, ... - OF_SENTINEL; + fallback: (id)fallback, ... OF_SENTINEL; /*! * @brief Returns the localized string for the specified ID, using the fallback * string if it cannot be looked up or is missing. * @@ -203,17 +203,18 @@ * @note Generally, you want to use @ref OF_LOCALIZED instead, which also takes * care of the `nil` sentinel automatically. * * @param ID The ID for the localized string * @param fallback The fallback to use in case the localized string cannot be - * looked up or is missing + * looked up or is missing. This can also be an array and use + * plural scripting, just like with the JSON language files. * @param arguments A va_list of arguments, consisting of pairs of variable * names and values to replace in the localized string, * terminated with `nil` * @return The localized string */ - (OFString *)localizedStringForID: (OFConstantString *)ID - fallback: (OFConstantString *)fallback + fallback: (id)fallback arguments: (va_list)arguments; @end OF_ASSUME_NONNULL_END Index: src/OFLocale.m ================================================================== --- src/OFLocale.m +++ src/OFLocale.m @@ -21,23 +21,25 @@ #import "OFLocale.h" #import "OFString.h" #import "OFArray.h" #import "OFDictionary.h" +#import "OFNumber.h" #import "OFInitializationFailedException.h" #import "OFInvalidArgumentException.h" -#import "OFInvalidEncodingException.h" +#import "OFInvalidFormatException.h" #import "OFOpenItemFailedException.h" #ifdef OF_AMIGAOS # include # include # include #endif static OFLocale *currentLocale = nil; +static OFDictionary *operatorPrecedences = nil; #ifndef OF_AMIGAOS static void parseLocale(char *locale, of_string_encoding_t *encoding, OFString **language, OFString **territory) @@ -60,11 +62,11 @@ @try { if (encoding != NULL) *encoding = of_string_parse_encoding( [OFString stringWithCString: tmp encoding: enc]); - } @catch (OFInvalidEncodingException *e) { + } @catch (OFInvalidArgumentException *e) { } } /* Territory */ if ((tmp = strrchr(locale, '_')) != NULL) { @@ -81,14 +83,262 @@ } @finally { free(locale); } } #endif + +static bool +evaluateCondition(OFString *condition, OFDictionary *variables) +{ + OFMutableArray *tokens, *operators, *stack; + + /* Empty condition is the fallback that's always true */ + if (condition.length == 0) + return true; + + /* + * Dirty hack to allow not needing spaces after "!" or "(" and spaces + * before ")". + * TODO: Replace with a proper tokenizer. + */ + condition = [condition stringByReplacingOccurrencesOfString: @"!" + withString: @"! "]; + condition = [condition stringByReplacingOccurrencesOfString: @"(" + withString: @"( "]; + condition = [condition stringByReplacingOccurrencesOfString: @")" + withString: @" )"]; + + /* Substitute variables and convert to RPN first */ + tokens = [OFMutableArray array]; + operators = [OFMutableArray array]; + for (OFString *token in [condition + componentsSeparatedByString: @" " + options: OF_STRING_SKIP_EMPTY]) { + unsigned precedence; + of_unichar_t c; + + if ([token isEqual: @"("]) { + [operators addObject: @"("]; + continue; + } + + if ([token isEqual: @")"]) { + for (;;) { + OFString *operator = operators.lastObject; + if (operator == nil) + @throw [OFInvalidFormatException + exception]; + + if ([operator isEqual: @"("]) { + [operators removeLastObject]; + break; + } + + [tokens addObject: operator]; + [operators removeLastObject]; + } + continue; + } + + precedence = [[operatorPrecedences objectForKey: token] + unsignedIntValue]; + if (precedence > 0) { + for (;;) { + OFNumber *operator = operators.lastObject; + unsigned otherPrecedence; + + if (operator == nil || [operator isEqual: @"("]) + break; + + otherPrecedence = [[operatorPrecedences + objectForKey: operator] unsignedIntValue]; + if (otherPrecedence >= precedence) + break; + + [tokens addObject: operator]; + [operators removeLastObject]; + } + + [operators addObject: token]; + continue; + } + + c = [token characterAtIndex: 0]; + if ((c < '0' || c > '9') && c != '-') + if ((token = [variables objectForKey: token]) == nil) + @throw [OFInvalidFormatException exception]; + + [tokens addObject: + [OFNumber numberWithDouble: token.doubleValue]]; + } + for (size_t i = operators.count; i > 0; i--) { + OFString *operator = [operators objectAtIndex: i - 1]; + + if ([operator isEqual: @"("]) + @throw [OFInvalidFormatException exception]; + + [tokens addObject: operator]; + } + + /* Evaluate RPN */ + stack = [OFMutableArray array]; + for (id token in tokens) { + unsigned precedence = [[operatorPrecedences + objectForKey: token] unsignedIntValue]; + id var, first, second; + size_t stackSize; + + /* Only unary operators have precedence 1 */ + if (precedence > 1) { + stackSize = stack.count; + first = [stack objectAtIndex: stackSize - 2]; + second = [stack objectAtIndex: stackSize - 1]; + + if ([token isEqual: @"=="]) + var = [OFNumber numberWithBool: + [first isEqual: second]]; + else if ([token isEqual: @"!="]) + var = [OFNumber numberWithBool: + ![first isEqual: second]]; + else if ([token isEqual: @"<"]) + var = [OFNumber numberWithBool: [first + compare: second] == OF_ORDERED_ASCENDING]; + else if ([token isEqual: @"<="]) + var = [OFNumber numberWithBool: [first + compare: second] != OF_ORDERED_DESCENDING]; + else if ([token isEqual: @">"]) + var = [OFNumber numberWithBool: [first + compare: second] == OF_ORDERED_DESCENDING]; + else if ([token isEqual: @">="]) + var = [OFNumber numberWithBool: [first + compare: second] != OF_ORDERED_ASCENDING]; + else if ([token isEqual: @"+"]) + var = [OFNumber numberWithDouble: + [first doubleValue] + [second doubleValue]]; + else if ([token isEqual: @"%"]) + var = [OFNumber numberWithIntMax: + [first intMaxValue] % [second intMaxValue]]; + else if ([token isEqual: @"&&"]) + var = [OFNumber numberWithBool: + [first boolValue] && [second boolValue]]; + else if ([token isEqual: @"||"]) + var = [OFNumber numberWithBool: + [first boolValue] || [second boolValue]]; + else + OF_ENSURE(0); + + [stack replaceObjectAtIndex: stackSize - 2 + withObject: var]; + [stack removeLastObject]; + } else if (precedence == 1) { + stackSize = stack.count; + first = stack.lastObject; + + if ([token isEqual: @"!"]) + var = [OFNumber numberWithBool: + ![first boolValue]]; + else if ([token isEqual: @"is_real"]) + var = [OFNumber numberWithBool: + [first doubleValue] != [first intMaxValue]]; + else + OF_ENSURE(0); + + [stack replaceObjectAtIndex: stackSize - 1 + withObject: var]; + } else + [stack addObject: token]; + } + + if (stack.count != 1) + @throw [OFInvalidFormatException exception]; + + return [stack.firstObject boolValue]; +} + +static OFString * +evaluateConditionals(OFArray *conditions, OFDictionary *variables) +{ + for (OFDictionary *dictionary in conditions) { + OFString *condition, *value; + bool found = false; + + for (OFString *key in dictionary) { + if (found) + @throw [OFInvalidFormatException exception]; + + condition = key; + value = [dictionary objectForKey: key]; + + if (![condition isKindOfClass: [OFString class]] || + ![value isKindOfClass: [OFString class]]) + @throw [OFInvalidFormatException exception]; + + found = true; + } + if (!found) + @throw [OFInvalidFormatException exception]; + + if (evaluateCondition(condition, variables)) + return value; + } + + /* Need to have a fallback as the last one. */ + @throw [OFInvalidFormatException exception]; +} + +static OFString * +evaluateArray(OFArray *array, OFDictionary *variables) +{ + OFMutableString *string = [OFMutableString string]; + + for (id object in array) { + if ([object isKindOfClass: [OFString class]]) + [string appendString: object]; + else if ([object isKindOfClass: [OFArray class]]) + [string appendString: + evaluateConditionals(object, variables)]; + else + @throw [OFInvalidFormatException exception]; + } + + [string makeImmutable]; + + return string; +} @implementation OFLocale @synthesize language = _language, territory = _territory, encoding = _encoding; @synthesize decimalPoint = _decimalPoint; + ++ (void)initialize +{ + OFNumber *one, *two, *three, *four; + + if (self != [OFLocale class]) + return; + + /* 1 is also used to denote a unary operator. */ + one = [OFNumber numberWithUnsignedInt: 1]; + two = [OFNumber numberWithUnsignedInt: 2]; + three = [OFNumber numberWithUnsignedInt: 3]; + four = [OFNumber numberWithUnsignedInt: 4]; + + operatorPrecedences = [[OFDictionary alloc] initWithKeysAndObjects: + @"==", two, + @"!=", two, + @"<", two, + @"<=", two, + @">", two, + @">=", two, + @"+", two, + @"%", two, + @"&&", three, + @"||", four, + @"!", one, + @"is_real", one, + nil]; +} + (OFLocale *)currentLocale { return currentLocale; } @@ -180,11 +430,11 @@ @try { _encoding = of_string_parse_encoding( [OFString stringWithCString: buffer encoding: ASCII]); - } @catch (OFInvalidEncodingException *e) { + } @catch (OFInvalidArgumentException *e) { _encoding = OF_STRING_ENCODING_ISO_8859_1; } } else _encoding = OF_STRING_ENCODING_ISO_8859_1; @@ -203,25 +453,22 @@ initWithCString: buffer encoding: _encoding]; if ((locale = OpenLocale(NULL)) != NULL) { @try { - union { - uint32_t u32; - char c[4]; - } territory; + uint32_t territory; size_t length; - territory.u32 = + territory = OF_BSWAP32_IF_LE(locale->loc_CountryCode); for (length = 0; length < 4; length++) - if (territory.c[length] == 0) + if (((char *)&territory)[length] == 0) break; _territory = [[OFString alloc] - initWithCString: territory.c + initWithCString: (char *)&territory encoding: _encoding length: length]; } @finally { CloseLocale(locale); } @@ -293,11 +540,11 @@ objc_autoreleasePoolPop(pool); } #endif - (OFString *)localizedStringForID: (OFConstantString *)ID - fallback: (OFConstantString *)fallback, ... + fallback: (id)fallback, ... { OFString *ret; va_list args; va_start(args, fallback); @@ -308,36 +555,46 @@ return ret; } - (OFString *)localizedStringForID: (OFConstantString *)ID - fallback: (OFConstantString *)fallback + fallback: (id)fallback arguments: (va_list)arguments { OFMutableString *ret = [OFMutableString string]; void *pool = objc_autoreleasePoolPush(); + OFMutableDictionary *variables; + OFConstantString *name; const char *UTF8String = NULL; size_t last, UTF8StringLength; int state = 0; + + variables = [OFMutableDictionary dictionary]; + while ((name = va_arg(arguments, OFConstantString *)) != nil) + [variables setObject: va_arg(arguments, id) + forKey: name]; for (OFDictionary *strings in _localizedStrings) { id string = [strings objectForKey: ID]; if (string == nil) continue; if ([string isKindOfClass: [OFArray class]]) - string = [string componentsJoinedByString: @""]; + string = evaluateArray(string, variables); UTF8String = [string UTF8String]; UTF8StringLength = [string UTF8StringLength]; break; } if (UTF8String == NULL) { - UTF8String = fallback.UTF8String; - UTF8StringLength = fallback.UTF8StringLength; + if ([fallback isKindOfClass: [OFArray class]]) + fallback = evaluateArray(fallback, variables); + + UTF8String = [fallback UTF8String]; + UTF8StringLength = [fallback UTF8StringLength]; } state = 0; last = 0; for (size_t i = 0; i < UTF8StringLength; i++) { @@ -360,38 +617,17 @@ state = 0; } break; case 2: if (UTF8String[i] == ']') { - va_list argsCopy; - OFConstantString *name; - OFString *var = [OFString stringWithUTF8String: UTF8String + last length: i - last]; - - /* - * We loop, as most of the time, we only have - * one or maybe two variables, meaning looping - * is faster than constructing a dictionary. - */ - va_copy(argsCopy, arguments); - while ((name = va_arg(argsCopy, - OFConstantString *)) != nil) { - id value = va_arg(argsCopy, id); - - if (value == nil) - @throw - [OFInvalidArgumentException - exception]; - - if ([name isEqual: var]) { - [ret appendString: - [value description]]; - break; - } - } + OFString *value = [variables objectForKey: var]; + + if (value != nil) + [ret appendString: value.description]; last = i + 1; state = 0; } break; Index: src/OFMD5Hash.h ================================================================== --- src/OFMD5Hash.h +++ src/OFMD5Hash.h @@ -32,11 +32,11 @@ OFSecureData *_iVarsData; struct of_md5_hash_ivars { uint32_t state[4]; uint64_t bits; union of_md5_hash_buffer { - uint8_t bytes[64]; + unsigned char bytes[64]; uint32_t words[16]; } buffer; size_t bufferLength; } *_iVars; bool _allowsSwappableMemory; Index: src/OFMapTable.h ================================================================== --- src/OFMapTable.h +++ src/OFMapTable.h @@ -25,21 +25,22 @@ /*! * @struct of_map_table_functions_t OFMapTable.h ObjFW/OFMapTable.h * * @brief A struct describing the functions to be used by the map table. */ -typedef struct { +struct of_map_table_functions_t { /*! The function to retain keys / objects */ void *_Nullable (*_Nullable retain)(void *_Nullable object); /*! The function to release keys / objects */ void (*_Nullable release)(void *_Nullable object); /*! The function to hash keys */ uint32_t (*_Nullable hash)(void *_Nullable object); /*! The function to compare keys / objects */ bool (*_Nullable equal)(void *_Nullable object1, void *_Nullable object2); -} of_map_table_functions_t; +}; +typedef struct of_map_table_functions_t of_map_table_functions_t; #ifdef OF_HAVE_BLOCKS /*! * @brief A block for enumerating an OFMapTable. * Index: src/OFMutableArray.h ================================================================== --- src/OFMutableArray.h +++ src/OFMutableArray.h @@ -202,11 +202,11 @@ /*! * @brief Sorts the array using the specified selector and options. * * @param selector The selector to use to sort the array. It's signature - * should be the same as that of @ref OFComparing#compare:. + * should be the same as that of -[compare:]. * @param options The options to use when sorting the array.@n * Possible values are: * Value | Description * ---------------------------|------------------------- * `OF_ARRAY_SORT_DESCENDING` | Sort in descending order Index: src/OFMutableDictionary.h ================================================================== --- src/OFMutableDictionary.h +++ src/OFMutableDictionary.h @@ -66,11 +66,11 @@ - (instancetype)initWithCapacity: (size_t)capacity; /*! * @brief Sets an object for a key. * - * A key can be any object that conforms to the @ref OFCopying protocol. + * A key can be any object that conforms to the OFCopying protocol. * * @param key The key to set * @param object The object to set the key to */ - (void)setObject: (ObjectType)object @@ -77,11 +77,11 @@ forKey: (KeyType)key; /*! * @brief Sets an object for a key. * - * A key can be any object that conforms to the @ref OFCopying protocol. + * A key can be any object that conforms to the OFCopying protocol. * * This method is also used by the subscripting syntax. * * @param key The key to set * @param object The object to set the key to. If it is nil, this is equal to Index: src/OFMutableString.h ================================================================== --- src/OFMutableString.h +++ src/OFMutableString.h @@ -13,14 +13,18 @@ * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ -#import "OFString.h" +#ifndef OBJFW_OF_MUTABLE_STRING_H +#define OBJFW_OF_MUTABLE_STRING_H + +#include "OFString.h" OF_ASSUME_NONNULL_BEGIN +#ifdef __OBJC__ /*! * @class OFMutableString OFString.h ObjFW/OFString.h * * @brief A class for storing and modifying strings. */ @@ -210,7 +214,10 @@ /*! * @brief Converts the mutable string to an immutable string. */ - (void)makeImmutable; @end +#endif OF_ASSUME_NONNULL_END + +#endif Index: src/OFNumber.m ================================================================== --- src/OFNumber.m +++ src/OFNumber.m @@ -556,31 +556,19 @@ _value.uIntMax = element.decimalValue; } else if ([typeString isEqual: @"signed"]) { _type = OF_NUMBER_TYPE_INTMAX; _value.intMax = element.decimalValue; } else if ([typeString isEqual: @"float"]) { - union { - float f; - uint32_t u; - } f; - - f.u = OF_BSWAP32_IF_LE( - (uint32_t)element.hexadecimalValue); - _type = OF_NUMBER_TYPE_FLOAT; - _value.float_ = OF_BSWAP_FLOAT_IF_LE(f.f); + _value.float_ = OF_BSWAP_FLOAT_IF_LE( + OF_INT_TO_FLOAT_RAW(OF_BSWAP32_IF_LE( + (uint32_t)element.hexadecimalValue))); } else if ([typeString isEqual: @"double"]) { - union { - double d; - uint64_t u; - } d; - - d.u = OF_BSWAP64_IF_LE( - (uint64_t)element.hexadecimalValue); - _type = OF_NUMBER_TYPE_DOUBLE; - _value.double_ = OF_BSWAP_DOUBLE_IF_LE(d.d); + _value.double_ = OF_BSWAP_DOUBLE_IF_LE( + OF_INT_TO_DOUBLE_RAW(OF_BSWAP64_IF_LE( + (uint64_t)element.hexadecimalValue))); } else @throw [OFInvalidArgumentException exception]; objc_autoreleasePoolPop(pool); } @catch (id e) { @@ -1073,22 +1061,19 @@ } OF_HASH_INIT(hash); if (type & OF_NUMBER_TYPE_FLOAT) { - union { - double d; - uint8_t b[sizeof(double)]; - } d; + double d; if (isnan(self.doubleValue)) return 0; - d.d = OF_BSWAP_DOUBLE_IF_BE(self.doubleValue); + d = OF_BSWAP_DOUBLE_IF_BE(self.doubleValue); for (uint_fast8_t i = 0; i < sizeof(double); i++) - OF_HASH_ADD(hash, d.b[i]); + OF_HASH_ADD(hash, ((char *)&d)[i]); } else if (type & OF_NUMBER_TYPE_SIGNED) { intmax_t v = self.intMaxValue * -1; while (v != 0) { OF_HASH_ADD(hash, v & 0xFF); @@ -1216,42 +1201,29 @@ case OF_NUMBER_TYPE_INT32: case OF_NUMBER_TYPE_INT64: case OF_NUMBER_TYPE_SSIZE: case OF_NUMBER_TYPE_INTMAX: case OF_NUMBER_TYPE_PTRDIFF: - case OF_NUMBER_TYPE_INTPTR:; + case OF_NUMBER_TYPE_INTPTR: [element addAttributeWithName: @"type" stringValue: @"signed"]; break; - case OF_NUMBER_TYPE_FLOAT:; - union { - float f; - uint32_t u; - } f; - - f.f = OF_BSWAP_FLOAT_IF_LE(_value.float_); - + case OF_NUMBER_TYPE_FLOAT: [element addAttributeWithName: @"type" stringValue: @"float"]; - element.stringValue = - [OFString stringWithFormat: @"%08" PRIx32, - OF_BSWAP32_IF_LE(f.u)]; + element.stringValue = [OFString stringWithFormat: @"%08" PRIx32, + OF_BSWAP32_IF_LE(OF_FLOAT_TO_INT_RAW(OF_BSWAP_FLOAT_IF_LE( + _value.float_)))]; break; - case OF_NUMBER_TYPE_DOUBLE:; - union { - double d; - uint64_t u; - } d; - - d.d = OF_BSWAP_DOUBLE_IF_LE(_value.double_); - + case OF_NUMBER_TYPE_DOUBLE: [element addAttributeWithName: @"type" stringValue: @"double"]; - element.stringValue = - [OFString stringWithFormat: @"%016" PRIx64, - OF_BSWAP64_IF_LE(d.u)]; + element.stringValue = [OFString + stringWithFormat: @"%016" PRIx64, + OF_BSWAP64_IF_LE(OF_DOUBLE_TO_INT_RAW(OF_BSWAP_DOUBLE_IF_LE( + _value.double_)))]; break; default: @throw [OFInvalidFormatException exception]; } Index: src/OFObject.h ================================================================== --- src/OFObject.h +++ src/OFObject.h @@ -13,10 +13,13 @@ * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ +#ifndef OBJFW_OF_OBJECT_H +#define OBJFW_OF_OBJECT_H + #include "objfw-defs.h" #ifndef __STDC_LIMIT_MACROS # define __STDC_LIMIT_MACROS #endif @@ -27,12 +30,12 @@ #include #include #include #include -#import "macros.h" -#import "block.h" +#include "macros.h" +#include "block.h" /* * Some versions of MinGW require to be included before * . Do this here to make sure this is always done in the correct * order, even if another header includes just . @@ -86,16 +89,17 @@ /*! * @struct of_range_t OFObject.h ObjFW/OFObject.h * * @brief A range. */ -typedef struct OF_BOXABLE { +struct OF_BOXABLE of_range_t { /*! The start of the range */ size_t location; /*! The length of the range */ size_t length; -} of_range_t; +}; +typedef struct of_range_t of_range_t; /*! * @brief Creates a new of_range_t. * * @param start The starting index of the range @@ -137,16 +141,17 @@ /*! * @struct of_point_t OFObject.h ObjFW/OFObject.h * * @brief A point. */ -typedef struct OF_BOXABLE { +struct OF_BOXABLE of_point_t { /*! The x coordinate of the point */ float x; /*! The y coordinate of the point */ float y; -} of_point_t; +}; +typedef struct of_point_t of_point_t; /*! * @brief Creates a new of_point_t. * * @param x The x coordinate of the point @@ -183,16 +188,17 @@ /*! * @struct of_dimension_t OFObject.h ObjFW/OFObject.h * * @brief A dimension. */ -typedef struct OF_BOXABLE { +struct OF_BOXABLE of_dimension_t { /*! The width of the dimension */ float width; /*! The height of the dimension */ float height; -} of_dimension_t; +}; +typedef struct of_dimension_t of_dimension_t; /*! * @brief Creates a new of_dimension_t. * * @param width The width of the dimension @@ -229,16 +235,17 @@ /*! * @struct of_rectangle_t OFObject.h ObjFW/OFObject.h * * @brief A rectangle. */ -typedef struct OF_BOXABLE { +struct OF_BOXABLE of_rectangle_t { /*! The point from where the rectangle originates */ of_point_t origin; /*! The size of the rectangle */ of_dimension_t size; -} of_rectangle_t; +}; +typedef struct of_rectangle_t of_rectangle_t; /*! * @brief Creates a new of_rectangle_t. * * @param x The x coordinate of the top left corner of the rectangle @@ -275,10 +282,11 @@ return false; return true; } +#ifdef __OBJC__ @class OFMethodSignature; @class OFString; @class OFThread; /*! @@ -288,15 +296,15 @@ */ @protocol OFObject /*! * @brief The class of the object. */ -#ifndef __cplusplus +# ifndef __cplusplus @property (readonly, nonatomic) Class class; -#else +# else @property (readonly, nonatomic, getter=class) Class class_; -#endif +# endif /*! * @brief The superclass of the object. */ @property OF_NULLABLE_PROPERTY (readonly, nonatomic) Class superclass; @@ -490,37 +498,39 @@ * * @return Whether a weak reference to this object has been retained */ - (bool)retainWeakReference; @end +#endif /*! * @class OFObject OFObject.h ObjFW/OFObject.h * * @brief The root class for all other classes inside ObjFW. */ +#ifdef __OBJC__ OF_ROOT_CLASS @interface OFObject { @private -#ifndef __clang_analyzer__ +# ifndef __clang_analyzer__ Class _isa; -#else +# else Class _isa __attribute__((__unused__)); -#endif +# endif } -#ifdef OF_HAVE_CLASS_PROPERTIES -# ifndef __cplusplus +# ifdef OF_HAVE_CLASS_PROPERTIES +# ifndef __cplusplus @property (class, readonly, nonatomic) Class class; -# else +# else @property (class, readonly, nonatomic, getter=class) Class class_; -# endif +# endif @property (class, readonly, nonatomic) OFString *className; @property (class, readonly, nullable, nonatomic) Class superclass; @property (class, readonly, nonatomic) OFString *description; -#endif +# endif /*! * @brief The name of the object's class. */ @property (readonly, nonatomic) OFString *className; @@ -735,17 +745,35 @@ + (id)copy; /*! * @brief Initializes an already allocated object. * - * Derived classes may override this, but need to do + * Derived classes may override this, but need to use the following pattern: + * @code + * self = [super init]; + * + * @try { + * // Custom initialization code goes here. + * } @catch (id e) { + * [self release]; + * @throw e; + * } + * + * return self; + * @endcode + * + * With ARC enabled, the following pattern needs to be used instead: * @code - * self = [super init] + * self = [super init]; + * + * // Custom initialization code goes here. + * + * return self; * @endcode - * before they do any initialization themselves. @ref init may never return - * `nil`, instead an exception (for example @ref - * OFInitializationFailedException) should be thrown. + * + * @ref init may never return `nil`, instead an exception (for example + * @ref OFInitializationFailedException) should be thrown. * * @return An initialized object */ - (instancetype)init; @@ -935,11 +963,11 @@ withObject: (nullable id)object2 withObject: (nullable id)object3 withObject: (nullable id)object4 afterDelay: (of_time_interval_t)delay; -#ifdef OF_HAVE_THREADS +# ifdef OF_HAVE_THREADS /*! * @brief Performs the specified selector on the specified thread. * * @param selector The selector to perform * @param thread The thread on which to perform the selector @@ -1193,11 +1221,11 @@ withObject: (nullable id)object1 withObject: (nullable id)object2 withObject: (nullable id)object3 withObject: (nullable id)object4 afterDelay: (of_time_interval_t)delay; -#endif +# endif /*! * @brief This method is called when @ref resolveClassMethod: or * @ref resolveInstanceMethod: returned false. It should return a target * to which the message should be forwarded. @@ -1218,11 +1246,15 @@ * * @param selector The selector not understood by the receiver */ - (void)doesNotRecognizeSelector: (SEL)selector OF_NO_RETURN; @end +#else +typedef void OFObject; +#endif +#ifdef __OBJC__ /*! * @protocol OFCopying OFObject.h ObjFW/OFObject.h * * @brief A protocol for the creation of copies. */ @@ -1270,10 +1302,11 @@ * @param object An object to compare the object to * @return The result of the comparison */ - (of_comparison_result_t)compare: (id )object; @end +#endif #ifdef __cplusplus extern "C" { #endif #ifdef OF_APPLE_RUNTIME @@ -1288,7 +1321,11 @@ } #endif OF_ASSUME_NONNULL_END -#import "OFObject+KeyValueCoding.h" -#import "OFObject+Serialization.h" +#ifdef __OBJC__ +# import "OFObject+KeyValueCoding.h" +# import "OFObject+Serialization.h" +#endif + +#endif Index: src/OFOptionsParser.h ================================================================== --- src/OFOptionsParser.h +++ src/OFOptionsParser.h @@ -25,11 +25,11 @@ /*! * @struct of_options_parser_option_t OFOptionsParser.h ObjFW/OFOptionsParser.h * * @brief An option which can be parsed by an @ref OFOptionsParser. */ -typedef struct { +struct of_options_parser_option_t { /*! The short version (e.g. `-v`) of the option or `\0` for none. */ of_unichar_t shortOption; /*! * The long version (e.g. `--verbose`) of the option or `nil` for none. @@ -53,15 +53,16 @@ * been specified. */ bool *_Nullable isSpecifiedPtr; /*! - * An optional pointer to an @ref OFString * that is set to the + * An optional pointer to an `OFString *` that is set to the * argument specified for the option or `nil` for no argument. */ OFString *__autoreleasing _Nullable *_Nullable argumentPtr; -} of_options_parser_option_t; +}; +typedef struct of_options_parser_option_t of_options_parser_option_t; /*! * @class OFOptionsParser OFOptionsParser.h ObjFW/OFOptionsParser.h * * @brief A class for parsing the program options specified on the command line. Index: src/OFPlugin.m ================================================================== --- src/OFPlugin.m +++ src/OFPlugin.m @@ -23,12 +23,13 @@ #ifdef HAVE_DLFCN_H # include #endif #import "OFPlugin.h" -#import "OFString.h" #import "OFLocale.h" +#import "OFString.h" +#import "OFSystemInfo.h" #import "OFInitializationFailedException.h" #import "OFLoadPluginFailedException.h" typedef OFPlugin *(*init_plugin_t)(void); @@ -40,11 +41,15 @@ return dlopen([path cStringWithEncoding: [OFLocale encoding]], flags); #else if (path == nil) return GetModuleHandle(NULL); - return LoadLibraryW(path.UTF16String); + if ([OFSystemInfo isWindowsNT]) + return LoadLibraryW(path.UTF16String); + else + return LoadLibraryA( + [path cStringWithEncoding: [OFLocale encoding]]); #endif } void * of_dlsym(of_plugin_handle_t handle, const char *symbol) Index: src/OFProcess.h ================================================================== --- src/OFProcess.h +++ src/OFProcess.h @@ -195,8 +195,15 @@ * This method needs to be called for some programs before data can be read, * since some programs don't start processing before the write direction is * closed. */ - (void)closeForWriting; + +/*! + * @brief Waits for the process to terminate and returns the exit status. + * + * If the process has already exited, this returns the exit status immediately. + */ +- (int)waitForTermination; @end OF_ASSUME_NONNULL_END Index: src/OFRIPEMD160Hash.h ================================================================== --- src/OFRIPEMD160Hash.h +++ src/OFRIPEMD160Hash.h @@ -32,11 +32,11 @@ OFSecureData *_iVarsData; struct of_ripemd160_hash_ivars { uint32_t state[5]; uint64_t bits; union of_ripemd160_hash_buffer { - uint8_t bytes[64]; + unsigned char bytes[64]; uint32_t words[16]; } buffer; size_t bufferLength; } *_iVars; bool _allowsSwappableMemory; Index: src/OFRunLoop+Private.h ================================================================== --- src/OFRunLoop+Private.h +++ src/OFRunLoop+Private.h @@ -16,20 +16,22 @@ */ #import "OFRunLoop.h" #import "OFStream.h" #ifdef OF_HAVE_SOCKETS -# import "OFTCPSocket.h" -# import "OFUDPSocket.h" +# import "OFDatagramSocket.h" +# import "OFSequencedPacketSocket.h" +# import "OFStreamSocket.h" #endif OF_ASSUME_NONNULL_BEGIN #ifdef OF_HAVE_SOCKETS -@protocol OFTCPSocketDelegate_Private -- (void)of_socketDidConnect: (OFTCPSocket *)socket +@protocol OFRunLoopConnectDelegate +- (void)of_socketDidConnect: (id)socket exception: (nullable id)exception; +- (id)of_connectionFailedExceptionForErrNo: (int)errNo; @end #endif @interface OFRunLoop () + (void)of_setMainRunLoop: (OFRunLoop *)runLoop; @@ -81,49 +83,54 @@ of_stream_async_write_string_block_t) block # endif delegate: (nullable id )delegate; # if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) -+ (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)socket - mode: (of_run_loop_mode_t)mode - delegate: (id ) - delegate; -# endif -+ (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)socket - mode: (of_run_loop_mode_t)mode -# ifdef OF_HAVE_BLOCKS - block: (nullable - of_tcp_socket_async_accept_block_t) - block -# endif - delegate: (nullable id ) - delegate; -+ (void)of_addAsyncReceiveForUDPSocket: (OFUDPSocket *)socket - buffer: (void *)buffer - length: (size_t)length - mode: (of_run_loop_mode_t)mode -# ifdef OF_HAVE_BLOCKS - block: (nullable - of_udp_socket_async_receive_block_t) - block -# endif - delegate: (nullable id ) - delegate; -+ (void)of_addAsyncSendForUDPSocket: (OFUDPSocket *)socket - data: (OFData *)data - receiver: (const of_socket_address_t *)receiver - mode: (of_run_loop_mode_t)mode -# ifdef OF_HAVE_BLOCKS - block: (nullable - of_udp_socket_async_send_data_block_t) - block -# endif - delegate: (nullable id ) - delegate; ++ (void)of_addAsyncConnectForSocket: (id)socket + mode: (of_run_loop_mode_t)mode + delegate: (id )delegate; +# endif ++ (void)of_addAsyncAcceptForSocket: (id)socket + mode: (of_run_loop_mode_t)mode + block: (nullable id)block + delegate: (nullable id)delegate; ++ (void)of_addAsyncReceiveForDatagramSocket: (OFDatagramSocket *)socket + buffer: (void *)buffer + length: (size_t)length + mode: (of_run_loop_mode_t)mode +# ifdef OF_HAVE_BLOCKS + block: (nullable of_datagram_socket_async_receive_block_t)block +# endif + delegate: (nullable id ) delegate; ++ (void)of_addAsyncSendForDatagramSocket: (OFDatagramSocket *)socket + data: (OFData *)data + receiver: (const of_socket_address_t *)receiver + mode: (of_run_loop_mode_t)mode +# ifdef OF_HAVE_BLOCKS + block: (nullable of_datagram_socket_async_send_data_block_t)block +# endif + delegate: (nullable id )delegate; ++ (void)of_addAsyncReceiveForSequencedPacketSocket: + (OFSequencedPacketSocket *)socket + buffer: (void *)buffer + length: (size_t)length + mode: (of_run_loop_mode_t)mode +# ifdef OF_HAVE_BLOCKS + block: (nullable of_sequenced_packet_socket_async_receive_block_t)block +# endif + delegate: (nullable id ) delegate; ++ (void)of_addAsyncSendForSequencedPacketSocket: + (OFSequencedPacketSocket *)socket + data: (OFData *)data + mode: (of_run_loop_mode_t)mode +# ifdef OF_HAVE_BLOCKS + block: (nullable of_sequenced_packet_socket_async_send_data_block_t)block +# endif + delegate: (nullable id )delegate; + (void)of_cancelAsyncRequestsForObject: (id)object mode: (of_run_loop_mode_t)mode; #endif - (void)of_removeTimer: (OFTimer *)timer forMode: (of_run_loop_mode_t)mode; @end OF_ASSUME_NONNULL_END Index: src/OFRunLoop.m ================================================================== --- src/OFRunLoop.m +++ src/OFRunLoop.m @@ -25,12 +25,15 @@ #import "OFArray.h" #import "OFData.h" #import "OFDictionary.h" #ifdef OF_HAVE_SOCKETS # import "OFKernelEventObserver.h" -# import "OFTCPSocket.h" -# import "OFTCPSocket+Private.h" +# import "OFDatagramSocket.h" +# import "OFSequencedPacketSocket.h" +# import "OFSequencedPacketSocket+Private.h" +# import "OFStreamSocket.h" +# import "OFStreamSocket+Private.h" #endif #import "OFThread.h" #ifdef OF_HAVE_THREADS # import "OFMutex.h" # import "OFCondition.h" @@ -39,13 +42,10 @@ #import "OFTimer.h" #import "OFTimer+Private.h" #import "OFDate.h" #import "OFObserveFailedException.h" -#ifdef OF_HAVE_SOCKETS -# import "OFConnectionFailedException.h" -#endif of_run_loop_mode_t of_run_loop_mode_default = @"of_run_loop_mode_default"; static OFRunLoop *mainRunLoop = nil; @interface OFRunLoopState: OFObject @@ -155,34 +155,55 @@ @interface OFRunLoopAcceptQueueItem: OFRunLoopQueueItem { @public # ifdef OF_HAVE_BLOCKS - of_tcp_socket_async_accept_block_t _block; + id _block; # endif } @end + +@interface OFRunLoopDatagramReceiveQueueItem: OFRunLoopQueueItem +{ +@public +# ifdef OF_HAVE_BLOCKS + of_datagram_socket_async_receive_block_t _block; +# endif + void *_buffer; + size_t _length; +} +@end + +@interface OFRunLoopDatagramSendQueueItem: OFRunLoopQueueItem +{ +@public +# ifdef OF_HAVE_BLOCKS + of_datagram_socket_async_send_data_block_t _block; +# endif + OFData *_data; + of_socket_address_t _receiver; +} +@end -@interface OFRunLoopUDPReceiveQueueItem: OFRunLoopQueueItem +@interface OFRunLoopPacketReceiveQueueItem: OFRunLoopQueueItem { @public # ifdef OF_HAVE_BLOCKS - of_udp_socket_async_receive_block_t _block; + of_sequenced_packet_socket_async_receive_block_t _block; # endif void *_buffer; size_t _length; } @end -@interface OFRunLoopUDPSendQueueItem: OFRunLoopQueueItem +@interface OFRunLoopPacketSendQueueItem: OFRunLoopQueueItem { @public # ifdef OF_HAVE_BLOCKS - of_udp_socket_async_send_data_block_t _block; + of_sequenced_packet_socket_async_send_data_block_t _block; # endif OFData *_data; - of_socket_address_t _receiver; } @end #endif @implementation OFRunLoopState @@ -416,11 +437,11 @@ exception = e; } # ifdef OF_HAVE_BLOCKS if (_block != NULL) - return _block(object, _buffer, length, exception); + return _block(length, exception); else { # endif if (![_delegate respondsToSelector: @selector(stream:didReadIntoBuffer:length:exception:)]) return false; @@ -464,11 +485,11 @@ exception == nil) return true; # ifdef OF_HAVE_BLOCKS if (_block != NULL) { - if (!_block(object, _buffer, _readLength, exception)) + if (!_block(_readLength, exception)) return false; _readLength = 0; return true; } else { @@ -516,11 +537,11 @@ if (line == nil && ![object isAtEndOfStream] && exception == nil) return true; # ifdef OF_HAVE_BLOCKS if (_block != NULL) - return _block(object, line, exception); + return _block(line, exception); else { # endif if (![_delegate respondsToSelector: @selector(stream:didReadLine:exception:)]) return false; @@ -566,11 +587,11 @@ if (_writtenLength != dataLength && exception == nil) return true; # ifdef OF_HAVE_BLOCKS if (_block != NULL) { - newData = _block(object, _data, _writtenLength, exception); + newData = _block(_data, _writtenLength, exception); if (newData == nil) return false; oldData = _data; @@ -638,12 +659,11 @@ if (_writtenLength != cStringLength && exception == nil) return true; # ifdef OF_HAVE_BLOCKS if (_block != NULL) { - newString = _block(object, _string, _encoding, _writtenLength, - exception); + newString = _block(_string, _writtenLength, exception); if (newString == nil) return false; oldString = _string; @@ -695,15 +715,12 @@ { id exception = nil; int errNo; if ((errNo = [object of_socketError]) != 0) - exception = [OFConnectionFailedException - exceptionWithHost: nil - port: 0 - socket: object - errNo: errNo]; + exception = + [_delegate of_connectionFailedExceptionForErrNo: errNo]; if ([_delegate respondsToSelector: @selector(of_socketDidConnect:exception:)]) { /* * Make sure we only call the delegate once we removed the @@ -733,24 +750,32 @@ # endif @implementation OFRunLoopAcceptQueueItem - (bool)handleObject: (id)object { - OFTCPSocket *acceptedSocket; - id exception = nil; + id acceptedSocket, exception = nil; @try { acceptedSocket = [object accept]; } @catch (id e) { acceptedSocket = nil; exception = e; } # ifdef OF_HAVE_BLOCKS - if (_block != NULL) - return _block(object, acceptedSocket, exception); - else { + if (_block != NULL) { + if ([object isKindOfClass: [OFStreamSocket class]]) + return ((of_stream_socket_async_accept_block_t) + _block)(acceptedSocket, exception); + else if ([object isKindOfClass: + [OFSequencedPacketSocket class]]) + return + ((of_sequenced_packet_socket_async_accept_block_t) + _block)(acceptedSocket, exception); + else + OF_ENSURE(0); + } else { # endif if (![_delegate respondsToSelector: @selector(socket:didAcceptSocket:exception:)]) return false; @@ -770,11 +795,11 @@ [super dealloc]; } # endif @end -@implementation OFRunLoopUDPReceiveQueueItem +@implementation OFRunLoopDatagramReceiveQueueItem - (bool)handleObject: (id)object { size_t length; of_socket_address_t address; id exception = nil; @@ -788,11 +813,11 @@ exception = e; } # ifdef OF_HAVE_BLOCKS if (_block != NULL) - return _block(object, _buffer, length, &address, exception); + return _block(length, &address, exception); else { # endif if (![_delegate respondsToSelector: @selector( socket:didReceiveIntoBuffer:length:sender:exception:)]) return false; @@ -815,11 +840,11 @@ [super dealloc]; } # endif @end -@implementation OFRunLoopUDPSendQueueItem +@implementation OFRunLoopDatagramSendQueueItem - (bool)handleObject: (id)object { id exception = nil; OFData *newData, *oldData; @@ -831,11 +856,11 @@ exception = e; } # ifdef OF_HAVE_BLOCKS if (_block != NULL) { - newData = _block(object, _data, &_receiver, exception); + newData = _block(_data, &_receiver, exception); if (newData == nil) return false; oldData = _data; @@ -852,10 +877,111 @@ newData = [_delegate socket: object didSendData: _data receiver: &_receiver exception: exception]; + if (newData == nil) + return false; + + oldData = _data; + _data = [newData copy]; + [oldData release]; + + return true; +# ifdef OF_HAVE_BLOCKS + } +# endif +} + +- (void)dealloc +{ + [_data release]; +# ifdef OF_HAVE_BLOCKS + [_block release]; +# endif + + [super dealloc]; +} +@end + +@implementation OFRunLoopPacketReceiveQueueItem +- (bool)handleObject: (id)object +{ + size_t length; + id exception = nil; + + @try { + length = [object receiveIntoBuffer: _buffer + length: _length]; + } @catch (id e) { + length = 0; + exception = e; + } + +# ifdef OF_HAVE_BLOCKS + if (_block != NULL) + return _block(length, exception); + else { +# endif + if (![_delegate respondsToSelector: @selector( + socket:didReceiveIntoBuffer:length:exception:)]) + return false; + + return [_delegate socket: object + didReceiveIntoBuffer: _buffer + length: length + exception: exception]; +# ifdef OF_HAVE_BLOCKS + } +# endif +} + +# ifdef OF_HAVE_BLOCKS +- (void)dealloc +{ + [_block release]; + + [super dealloc]; +} +# endif +@end + +@implementation OFRunLoopPacketSendQueueItem +- (bool)handleObject: (id)object +{ + id exception = nil; + OFData *newData, *oldData; + + @try { + [object sendBuffer: _data.items + length: _data.count * _data.itemSize]; + } @catch (id e) { + exception = e; + } + +# ifdef OF_HAVE_BLOCKS + if (_block != NULL) { + newData = _block(_data, exception); + + if (newData == nil) + return false; + + oldData = _data; + _data = [newData copy]; + [oldData release]; + + return true; + } else { +# endif + if (![_delegate respondsToSelector: + @selector(socket:didSendData:exception:)]) + return false; + + newData = [_delegate socket: object + didSendData: _data + exception: exception]; + if (newData == nil) return false; oldData = _data; _data = [newData copy]; @@ -1050,51 +1176,90 @@ QUEUE_ITEM } # if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) -+ (void)of_addAsyncConnectForTCPSocket: (OFTCPSocket *)stream - mode: (of_run_loop_mode_t)mode - delegate: (id ) - delegate ++ (void)of_addAsyncConnectForSocket: (id)sock + mode: (of_run_loop_mode_t)mode + delegate: (id )delegate +{ + NEW_WRITE(OFRunLoopConnectQueueItem, sock, mode) + + queueItem->_delegate = [delegate retain]; + + QUEUE_ITEM +} +# endif + ++ (void)of_addAsyncAcceptForSocket: (id)sock + mode: (of_run_loop_mode_t)mode + block: (id)block + delegate: (id)delegate +{ + NEW_READ(OFRunLoopAcceptQueueItem, sock, mode) + + queueItem->_delegate = [delegate retain]; +# ifdef OF_HAVE_BLOCKS + queueItem->_block = [block copy]; +# endif + + QUEUE_ITEM +} + ++ (void)of_addAsyncReceiveForDatagramSocket: (OFDatagramSocket *)sock + buffer: (void *)buffer + length: (size_t)length + mode: (of_run_loop_mode_t)mode +# ifdef OF_HAVE_BLOCKS + block: (of_datagram_socket_async_receive_block_t)block +# endif + delegate: (id )delegate { - NEW_WRITE(OFRunLoopConnectQueueItem, stream, mode) + NEW_READ(OFRunLoopDatagramReceiveQueueItem, sock, mode) queueItem->_delegate = [delegate retain]; +# ifdef OF_HAVE_BLOCKS + queueItem->_block = [block copy]; +# endif + queueItem->_buffer = buffer; + queueItem->_length = length; QUEUE_ITEM } -# endif -+ (void)of_addAsyncAcceptForTCPSocket: (OFTCPSocket *)stream - mode: (of_run_loop_mode_t)mode ++ (void)of_addAsyncSendForDatagramSocket: (OFDatagramSocket *)sock + data: (OFData *)data + receiver: (const of_socket_address_t *)receiver + mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS - block: (of_tcp_socket_async_accept_block_t)block + block: (of_datagram_socket_async_send_data_block_t)block # endif - delegate: (id )delegate + delegate: (id )delegate { - NEW_READ(OFRunLoopAcceptQueueItem, stream, mode) + NEW_WRITE(OFRunLoopDatagramSendQueueItem, sock, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif + queueItem->_data = [data copy]; + queueItem->_receiver = *receiver; QUEUE_ITEM } -+ (void)of_addAsyncReceiveForUDPSocket: (OFUDPSocket *)sock - buffer: (void *)buffer - length: (size_t)length - mode: (of_run_loop_mode_t)mode ++ (void)of_addAsyncReceiveForSequencedPacketSocket: (OFSequencedPacketSocket *) + sock + buffer: (void *)buffer + length: (size_t)length + mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS - block: (of_udp_socket_async_receive_block_t) - block + block: (of_sequenced_packet_socket_async_receive_block_t)block # endif - delegate: (id )delegate + delegate: (id )delegate { - NEW_READ(OFRunLoopUDPReceiveQueueItem, sock, mode) + NEW_READ(OFRunLoopPacketReceiveQueueItem, sock, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif @@ -1102,28 +1267,25 @@ queueItem->_length = length; QUEUE_ITEM } -+ (void)of_addAsyncSendForUDPSocket: (OFUDPSocket *)sock - data: (OFData *)data - receiver: (const of_socket_address_t *)receiver - mode: (of_run_loop_mode_t)mode ++ (void)of_addAsyncSendForSequencedPacketSocket: (OFSequencedPacketSocket *)sock + data: (OFData *)data + mode: (of_run_loop_mode_t)mode # ifdef OF_HAVE_BLOCKS - block: (of_udp_socket_async_send_data_block_t) - block + block: (of_sequenced_packet_socket_async_send_data_block_t)block # endif - delegate: (id )delegate + delegate: (id )delegate { - NEW_WRITE(OFRunLoopUDPSendQueueItem, sock, mode) + NEW_WRITE(OFRunLoopPacketSendQueueItem, sock, mode) queueItem->_delegate = [delegate retain]; # ifdef OF_HAVE_BLOCKS queueItem->_block = [block copy]; # endif queueItem->_data = [data copy]; - queueItem->_receiver = *receiver; QUEUE_ITEM } # undef NEW_READ # undef NEW_WRITE ADDED src/OFSCTPSocket.h Index: src/OFSCTPSocket.h ================================================================== --- src/OFSCTPSocket.h +++ src/OFSCTPSocket.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFSequencedPacketSocket.h" +#import "OFRunLoop.h" + +#import "socket.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFSCTPSocket; +@class OFString; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when the socket connected. + * + * @param exception An exception which occurred while connecting the socket or + * `nil` on success + */ +typedef void (^of_sctp_socket_async_connect_block_t)(id _Nullable exception); +#endif + +/*! + * @protocol OFSCTPSocketDelegate OFSCTPSocket.h ObjFW/OFSCTPSocket.h + * + * A delegate for OFSCTPSocket. + */ +@protocol OFSCTPSocketDelegate +@optional +/*! + * @brief A method which is called when a socket connected. + * + * @param socket The socket which connected + * @param host The host connected to + * @param port The port on the host connected to + * @param exception An exception that occurred while connecting, or nil on + * success + */ +- (void)socket: (OFSCTPSocket *)socket + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (nullable id)exception; +@end + +/*! + * @class OFSCTPSocket OFSCTPSocket.h ObjFW/OFSCTPSocket.h + * + * @brief A class which provides methods to create and use SCTP sockets in + * one-to-one mode. + * + * To connect to a server, create a socket and connect it. + * To create a server, create a socket, bind it and listen on it. + */ +@interface OFSCTPSocket: OFSequencedPacketSocket +{ + OF_RESERVE_IVARS(4) +} + +/*! + * @brief Whether sending packets can be delayed. Setting this to NO sets + * SCTP_NODELAY on the socket. + */ +@property (nonatomic) bool canDelaySendingPackets; + +/*! + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + */ +- (void)connectToHost: (OFString *)host + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + * @param runLoopMode The run loop mode in which to perform the async connect + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + block: (of_sctp_socket_async_connect_block_t)block; + +/*! + * @brief Asynchronously connect the OFSCTPSocket to the specified destination. + * + * @param host The host to connect to + * @param port The port on the host to connect to + * @param runLoopMode The run loop mode in which to perform the async connect + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sctp_socket_async_connect_block_t)block; +#endif + +/*! + * @brief Bind the socket to the specified host and port. + * + * @param host The host to bind to. Use `@"0.0.0.0"` for IPv4 or `@"::"` for + * IPv6 to bind to all. + * @param port The port to bind to. If the port is 0, an unused port will be + * chosen, which can be obtained using the return value. + * @return The port the socket was bound to + */ +- (uint16_t)bindToHost: (OFString *)host + port: (uint16_t)port; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSCTPSocket.m Index: src/OFSCTPSocket.m ================================================================== --- src/OFSCTPSocket.m +++ src/OFSCTPSocket.m @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFSCTPSocket.h" +#import "OFDNSResolver.h" +#import "OFData.h" +#import "OFDate.h" +#import "OFIPSocketAsyncConnector.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" +#import "OFString.h" +#import "OFThread.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" +#import "OFGetOptionFailedException.h" +#import "OFNotOpenException.h" +#import "OFSetOptionFailedException.h" + +#import "socket.h" +#import "socket_helpers.h" + +static const of_run_loop_mode_t connectRunLoopMode = + @"of_sctp_socket_connect_mode"; + +@interface OFSCTPSocket () +@end + +@interface OFSCTPSocketConnectDelegate: OFObject +{ +@public + bool _done; + id _exception; +} +@end + +@implementation OFSCTPSocketConnectDelegate +- (void)dealloc +{ + [_exception release]; + + [super dealloc]; +} + +- (void)socket: (OFSCTPSocket *)sock + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (id)exception +{ + _done = true; + _exception = [exception retain]; +} +@end + +@implementation OFSCTPSocket +@dynamic delegate; + +- (bool)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + if ((_socket = socket(address->sockaddr.sockaddr.sa_family, + SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_SCTP)) == INVALID_SOCKET) { + *errNo = of_socket_errno(); + return false; + } + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + return true; +} + +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (connect(_socket, &address->sockaddr.sockaddr, + address->length) != 0) { + *errNo = of_socket_errno(); + return false; + } + + return true; +} + +- (void)of_closeSocket +{ + closesocket(_socket); + _socket = INVALID_SOCKET; +} + +- (void)connectToHost: (OFString *)host + port: (uint16_t)port +{ + void *pool = objc_autoreleasePoolPush(); + id delegate = _delegate; + OFSCTPSocketConnectDelegate *connectDelegate = + [[[OFSCTPSocketConnectDelegate alloc] init] autorelease]; + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + + self.delegate = connectDelegate; + [self asyncConnectToHost: host + port: port + runLoopMode: connectRunLoopMode]; + + while (!connectDelegate->_done) + [runLoop runMode: connectRunLoopMode + beforeDate: nil]; + + /* Cleanup */ + [runLoop runMode: connectRunLoopMode + beforeDate: [OFDate date]]; + + if (connectDelegate->_exception != nil) + @throw connectDelegate->_exception; + + self.delegate = delegate; + + objc_autoreleasePoolPop(pool); +} + +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port +{ + [self asyncConnectToHost: host + port: port + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + void *pool = objc_autoreleasePoolPush(); + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + [[[[OFIPSocketAsyncConnector alloc] + initWithSocket: self + host: host + port: port + delegate: _delegate + block: NULL + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + block: (of_sctp_socket_async_connect_block_t)block +{ + [self asyncConnectToHost: host + port: port + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncConnectToHost: (OFString *)host + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sctp_socket_async_connect_block_t)block +{ + void *pool = objc_autoreleasePoolPush(); + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + [[[[OFIPSocketAsyncConnector alloc] + initWithSocket: self + host: host + port: port + delegate: nil + block: block] autorelease] + startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} +#endif + +- (uint16_t)bindToHost: (OFString *)host + port: (uint16_t)port +{ + const int one = 1; + void *pool = objc_autoreleasePoolPush(); + OFData *socketAddresses; + of_socket_address_t address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + socketAddresses = [[OFThread DNSResolver] + resolveAddressesForHost: host + addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY]; + + address = *(of_socket_address_t *)[socketAddresses itemAtIndex: 0]; + of_socket_address_set_port(&address, port); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_SCTP)) == INVALID_SOCKET) + @throw [OFBindFailedException + exceptionWithHost: host + port: port + socket: self + errNo: of_socket_errno()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, + (char *)&one, (socklen_t)sizeof(one)); + + if (bind(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithHost: host + port: port + socket: self + errNo: errNo]; + } + + objc_autoreleasePoolPop(pool); + + if (port > 0) + return port; + + memset(&address, 0, sizeof(address)); + + address.length = (socklen_t)sizeof(address.sockaddr); + if (of_getsockname(_socket, &address.sockaddr.sockaddr, + &address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithHost: host + port: port + socket: self + errNo: errNo]; + } + + if (address.sockaddr.sockaddr.sa_family == AF_INET) + return OF_BSWAP16_IF_LE(address.sockaddr.in.sin_port); +# ifdef OF_HAVE_IPV6 + else if (address.sockaddr.sockaddr.sa_family == AF_INET6) + return OF_BSWAP16_IF_LE(address.sockaddr.in6.sin6_port); +# endif + else { + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithHost: host + port: port + socket: self + errNo: EAFNOSUPPORT]; + } +} + +- (void)setCanDelaySendingPackets: (bool)canDelaySendingPackets +{ + int v = !canDelaySendingPackets; + + if (setsockopt(_socket, IPPROTO_SCTP, SCTP_NODELAY, + (char *)&v, (socklen_t)sizeof(v)) != 0) + @throw [OFSetOptionFailedException + exceptionWithObject: self + errNo: of_socket_errno()]; +} + +- (bool)canDelaySendingPackets +{ + int v; + socklen_t len = sizeof(v); + + if (getsockopt(_socket, IPPROTO_SCTP, SCTP_NODELAY, + (char *)&v, &len) != 0 || len != sizeof(v)) + @throw [OFGetOptionFailedException + exceptionWithObject: self + errNo: of_socket_errno()]; + + return !v; +} +@end Index: src/OFSHA1Hash.h ================================================================== --- src/OFSHA1Hash.h +++ src/OFSHA1Hash.h @@ -32,11 +32,11 @@ OFSecureData *_iVarsData; struct of_sha1_hash_ivars { uint32_t state[5]; uint64_t bits; union of_sha1_hash_buffer { - uint8_t bytes[64]; + unsigned char bytes[64]; uint32_t words[80]; } buffer; size_t bufferLength; } *_iVars; bool _allowsSwappableMemory; Index: src/OFSHA224Or256Hash.h ================================================================== --- src/OFSHA224Or256Hash.h +++ src/OFSHA224Or256Hash.h @@ -33,11 +33,11 @@ @protected struct of_sha224_or_256_hash_ivars { uint32_t state[8]; uint64_t bits; union of_sha224_or_256_hash_buffer { - uint8_t bytes[64]; + unsigned char bytes[64]; uint32_t words[64]; } buffer; size_t bufferLength; } *_iVars; @private Index: src/OFSHA384Or512Hash.h ================================================================== --- src/OFSHA384Or512Hash.h +++ src/OFSHA384Or512Hash.h @@ -33,11 +33,11 @@ @protected struct of_sha384_or_512_hash_ivars { uint64_t state[8]; uint64_t bits[2]; union of_sha384_or_512_hash_buffer { - uint8_t bytes[128]; + unsigned char bytes[128]; uint64_t words[80]; } buffer; size_t bufferLength; } *_iVars; @private ADDED src/OFSPXSocket.h Index: src/OFSPXSocket.h ================================================================== --- src/OFSPXSocket.h +++ src/OFSPXSocket.h @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFSequencedPacketSocket.h" +#import "OFRunLoop.h" + +#import "socket.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFSPXSocket; +@class OFString; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when the socket connected. + * + * @param exception An exception which occurred while connecting the socket or + * `nil` on success + */ +typedef void (^of_spx_socket_async_connect_block_t)(id _Nullable exception); +#endif + +/*! + * @protocol OFSPXSocketDelegate OFSPXSocket.h ObjFW/OFSPXSocket.h + * + * A delegate for OFSPXSocket. + */ +@protocol OFSPXSocketDelegate +@optional +/*! + * @brief A method which is called when a socket connected. + * + * @param socket The socket which connected + * @param node The node the socket connected to + * @param network The network of the node the socket connected to + * @param port The port of the node to which the socket connected + * @param exception An exception that occurred while connecting, or nil on + * success + */ +- (void)socket: (OFSPXSocket *)socket + didConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + exception: (nullable id)exception; +@end + +/*! + * @class OFSPXSocket OFSPXSocket.h ObjFW/OFSPXSocket.h + * + * @brief A class which provides methods to create and use SPX sockets. + * + * @note If you want to use SPX in streaming mode instead of in message mode, + * use @ref OFSPXStreamSocket instead. + * + * To connect to a server, create a socket and connect it. + * To create a server, create a socket, bind it and listen on it. + */ +@interface OFSPXSocket: OFSequencedPacketSocket +{ + OF_RESERVE_IVARS(4) +} + +/*! + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Connect the OFSPXSocket to the specified destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + */ +- (void)connectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSPXSocket to the specified destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSPXSocket to the specified destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + * @param runLoopMode The run loop mode in which to perform the async connect + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously connect the OFSPXSocket to the specified destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + block: (of_spx_socket_async_connect_block_t)block; + +/*! + * @brief Asynchronously connect the OFSPXSocket to the specified destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + * @param runLoopMode The run loop mode in which to perform the async connect + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_spx_socket_async_connect_block_t)block; +#endif + +/*! + * @brief Bind the socket to the specified network, node and port. + * + * @param port The port (sometimes called socket number) to bind to. 0 means to + * pick one and return it. + * @return The address on which this socket can be reached + */ +- (of_socket_address_t)bindToPort: (uint16_t)port; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSPXSocket.m Index: src/OFSPXSocket.m ================================================================== --- src/OFSPXSocket.m +++ src/OFSPXSocket.m @@ -0,0 +1,388 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "OFSPXSocket.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" +#import "OFConnectionFailedException.h" +#import "OFNotOpenException.h" + +#import "socket.h" +#import "socket_helpers.h" + +#ifndef NSPROTO_SPX +# define NSPROTO_SPX 0 +#endif + +#define SPX_PACKET_TYPE 5 + +@interface OFSPXSocket () +- (int)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (void)of_closeSocket; +@end + +@interface OFSPXSocketAsyncConnectDelegate: OFObject +{ + OFSPXSocket *_socket; + unsigned char _node[IPX_NODE_LEN]; + uint32_t _network; + uint16_t _port; +#ifdef OF_HAVE_BLOCKS + of_spx_socket_async_connect_block_t _block; +#endif +} + +- (instancetype)initWithSocket: (OFSPXSocket *)socket + node: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +#ifdef OF_HAVE_BLOCKS + block: (of_spx_socket_async_connect_block_t)block +#endif +; +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +@end + +@implementation OFSPXSocketAsyncConnectDelegate +- (instancetype)initWithSocket: (OFSPXSocket *)sock + node: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +#ifdef OF_HAVE_BLOCKS + block: (of_spx_socket_async_connect_block_t)block +#endif +{ + self = [super init]; + + @try { + _socket = [sock retain]; + memcpy(_node, node, IPX_NODE_LEN); + _network = network; + _port = port; +#ifdef OF_HAVE_BLOCKS + _block = [block copy]; +#endif + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_socket release]; +#ifdef OF_HAVE_BLOCKS + [_block release]; +#endif + + [super dealloc]; +} + +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + of_socket_address_t address = + of_socket_address_ipx(_node, _network, _port); + id exception = nil; + int errNo; + + if (![_socket of_createSocketForAddress: &address + errNo: &errNo]) { + exception = [self of_connectionFailedExceptionForErrNo: errNo]; + goto inform_delegate; + } + + _socket.canBlock = false; + + if (![_socket of_connectSocketToAddress: &address + errNo: &errNo]) { + if (errNo == EINPROGRESS) { + [OFRunLoop of_addAsyncConnectForSocket: _socket + mode: runLoopMode + delegate: self]; + return; + } + + [_socket of_closeSocket]; + + exception = [self of_connectionFailedExceptionForErrNo: errNo]; + } + +inform_delegate: + [self performSelector: @selector(of_socketDidConnect:exception:) + withObject: _socket + withObject: exception + afterDelay: 0]; +} + +- (void)of_socketDidConnect: (id)sock + exception: (id)exception +{ + id delegate = ((OFSPXSocket *)sock).delegate; + + if (exception == nil) + ((OFSPXSocket *)sock).canBlock = true; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) + _block(exception); + else { +#endif + if ([delegate respondsToSelector: + @selector(socket:didConnectToNode:network:port:exception:)]) + [delegate socket: _socket + didConnectToNode: _node + network: _network + port: _port + exception: exception]; +#ifdef OF_HAVE_BLOCKS + } +#endif +} + +- (id)of_connectionFailedExceptionForErrNo: (int)errNo +{ + return [OFConnectionFailedException exceptionWithNode: _node + network: _network + port: _port + socket: _socket + errNo: errNo]; +} +@end + +@implementation OFSPXSocket +@dynamic delegate; + +- (int)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + if ((_socket = socket(address->sockaddr.ipx.sipx_family, + SOCK_SEQPACKET | SOCK_CLOEXEC, NSPROTO_SPX)) == INVALID_SOCKET) { + *errNo = of_socket_errno(); + return false; + } + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + return true; +} + +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (connect(_socket, &address->sockaddr.sockaddr, + address->length) != 0) { + *errNo = of_socket_errno(); + return false; + } + + return true; +} + +- (void)of_closeSocket +{ + closesocket(_socket); + _socket = INVALID_SOCKET; +} + +- (void)connectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +{ + of_socket_address_t address = + of_socket_address_ipx(node, network, port); + int errNo; + + if (![self of_createSocketForAddress: &address + errNo: &errNo]) + @throw [OFConnectionFailedException + exceptionWithNode: node + network: network + port: port + socket: self + errNo: errNo]; + + if (![self of_connectSocketToAddress: &address + errNo: &errNo]) { + [self of_closeSocket]; + + @throw [OFConnectionFailedException + exceptionWithNode: node + network: network + port: port + socket: self + errNo: errNo]; + } +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +{ + [self asyncConnectToNode: node + network: network + port: port + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + void *pool = objc_autoreleasePoolPush(); + + [[[[OFSPXSocketAsyncConnectDelegate alloc] + initWithSocket: self + node: node + network: network + port: port +#ifdef OF_HAVE_BLOCKS + block: NULL +#endif + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + block: (of_spx_socket_async_connect_block_t)block +{ + [self asyncConnectToNode: node + network: network + port: port + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_spx_socket_async_connect_block_t)block +{ + void *pool = objc_autoreleasePoolPush(); + + [[[[OFSPXSocketAsyncConnectDelegate alloc] + initWithSocket: self + node: node + network: network + port: port + block: block + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} +#endif + +- (of_socket_address_t)bindToPort: (uint16_t)port +{ + const unsigned char zeroNode[IPX_NODE_LEN] = { 0 }; + of_socket_address_t address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = of_socket_address_ipx(zeroNode, 0, port); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_SEQPACKET | SOCK_CLOEXEC, NSPROTO_SPX)) == INVALID_SOCKET) + @throw [OFBindFailedException + exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: of_socket_errno()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + if (bind(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: errNo]; + } + + memset(&address, 0, sizeof(address)); + address.family = OF_SOCKET_ADDRESS_FAMILY_IPX; + address.length = (socklen_t)sizeof(address.sockaddr); + + if (of_getsockname(_socket, &address.sockaddr.sockaddr, + &address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: errNo]; + } + + if (address.sockaddr.sockaddr.sa_family != AF_IPX) { + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: EAFNOSUPPORT]; + } + + return address; +} +@end ADDED src/OFSPXStreamSocket.h Index: src/OFSPXStreamSocket.h ================================================================== --- src/OFSPXStreamSocket.h +++ src/OFSPXStreamSocket.h @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFStreamSocket.h" +#import "OFRunLoop.h" + +#import "socket.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFSPXStreamSocket; +@class OFString; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when the socket connected. + * + * @param exception An exception which occurred while connecting the socket or + * `nil` on success + */ +typedef void (^of_spx_stream_socket_async_connect_block_t)( + id _Nullable exception); +#endif + +/*! + * @protocol OFSPXStreamSocketDelegate OFSPXStreamSocket.h \ + * ObjFW/OFSPXStreamSocket.h + * + * A delegate for OFSPXStreamSocket. + */ +@protocol OFSPXStreamSocketDelegate +@optional +/*! + * @brief A method which is called when a socket connected. + * + * @param socket The socket which connected + * @param node The node the socket connected to + * @param network The network of the node the socket connected to + * @param port The port of the node to which the socket connected + * @param exception An exception that occurred while connecting, or nil on + * success + */ +- (void)socket: (OFSPXStreamSocket *)socket + didConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + exception: (nullable id)exception; +@end + +/*! + * @class OFSPXStreamSocket OFSPXStreamSocket.h ObjFW/OFSPXStreamSocket.h + * + * @brief A class which provides methods to create and use SPX stream sockets. + * + * @note If you want to use SPX in message mode instead of in streaming mode, + * use @ref OFSPXSocket instead. + * + * To connect to a server, create a socket and connect it. + * To create a server, create a socket, bind it and listen on it. + */ +@interface OFSPXStreamSocket: OFStreamSocket +{ + OF_RESERVE_IVARS(4) +} + +/*! + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Connect the OFSPXStreamSocket to the specified destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + */ +- (void)connectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSPXStreamSocket to the specified + * destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port; + +/*! + * @brief Asynchronously connect the OFSPXStreamSocket to the specified + * destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + * @param runLoopMode The run loop mode in which to perform the async connect + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously connect the OFSPXStreamSocket to the specified + * destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + block: (of_spx_stream_socket_async_connect_block_t)block; + +/*! + * @brief Asynchronously connect the OFSPXStreamSocket to the specified + * destination. + * + * @param node The node to connect to + * @param network The network on which the node to connect to is + * @param port The port (sometimes also called socket number) on the node to + * connect to + * @param runLoopMode The run loop mode in which to perform the async connect + * @param block The block to execute once the connection has been established + */ +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_spx_stream_socket_async_connect_block_t)block; +#endif + +/*! + * @brief Bind the socket to the specified network, node and port. + * + * @param port The port (sometimes called socket number) to bind to. 0 means to + * pick one and return it. + * @return The address on which this socket can be reached + */ +- (of_socket_address_t)bindToPort: (uint16_t)port; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSPXStreamSocket.m Index: src/OFSPXStreamSocket.m ================================================================== --- src/OFSPXStreamSocket.m +++ src/OFSPXStreamSocket.m @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "OFSPXStreamSocket.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" + +#import "OFAlreadyConnectedException.h" +#import "OFBindFailedException.h" +#import "OFConnectionFailedException.h" +#import "OFNotOpenException.h" + +#import "socket.h" +#import "socket_helpers.h" + +#ifndef NSPROTO_SPX +# define NSPROTO_SPX 0 +#endif + +#define SPX_PACKET_TYPE 5 + +@interface OFSPXStreamSocket () +- (int)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo; +- (void)of_closeSocket; +@end + +@interface OFSPXStreamSocketAsyncConnectDelegate: OFObject + +{ + OFSPXStreamSocket *_socket; + unsigned char _node[IPX_NODE_LEN]; + uint32_t _network; + uint16_t _port; +#ifdef OF_HAVE_BLOCKS + of_spx_stream_socket_async_connect_block_t _block; +#endif +} + +- (instancetype)initWithSocket: (OFSPXStreamSocket *)socket + node: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +#ifdef OF_HAVE_BLOCKS + block: (of_spx_stream_socket_async_connect_block_t) + block +#endif +; +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; +@end + +@implementation OFSPXStreamSocketAsyncConnectDelegate +- (instancetype)initWithSocket: (OFSPXStreamSocket *)sock + node: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +#ifdef OF_HAVE_BLOCKS + block: (of_spx_stream_socket_async_connect_block_t) + block +#endif +{ + self = [super init]; + + @try { + _socket = [sock retain]; + memcpy(_node, node, IPX_NODE_LEN); + _network = network; + _port = port; +#ifdef OF_HAVE_BLOCKS + _block = [block copy]; +#endif + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_socket release]; +#ifdef OF_HAVE_BLOCKS + [_block release]; +#endif + + [super dealloc]; +} + +- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + of_socket_address_t address = + of_socket_address_ipx(_node, _network, _port); + id exception = nil; + int errNo; + + if (![_socket of_createSocketForAddress: &address + errNo: &errNo]) { + exception = [self of_connectionFailedExceptionForErrNo: errNo]; + goto inform_delegate; + } + + _socket.canBlock = false; + + if (![_socket of_connectSocketToAddress: &address + errNo: &errNo]) { + if (errNo == EINPROGRESS) { + [OFRunLoop of_addAsyncConnectForSocket: _socket + mode: runLoopMode + delegate: self]; + return; + } + + [_socket of_closeSocket]; + + exception = [self of_connectionFailedExceptionForErrNo: errNo]; + } + +inform_delegate: + [self performSelector: @selector(of_socketDidConnect:exception:) + withObject: _socket + withObject: exception + afterDelay: 0]; +} + +- (void)of_socketDidConnect: (id)sock + exception: (id)exception +{ + id delegate = + ((OFSPXStreamSocket *)sock).delegate; + + if (exception == nil) + ((OFSPXStreamSocket *)sock).canBlock = true; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) + _block(exception); + else { +#endif + if ([delegate respondsToSelector: + @selector(socket:didConnectToNode:network:port:exception:)]) + [delegate socket: _socket + didConnectToNode: _node + network: _network + port: _port + exception: exception]; +#ifdef OF_HAVE_BLOCKS + } +#endif +} + +- (id)of_connectionFailedExceptionForErrNo: (int)errNo +{ + return [OFConnectionFailedException exceptionWithNode: _node + network: _network + port: _port + socket: _socket + errNo: errNo]; +} +@end + +@implementation OFSPXStreamSocket +@dynamic delegate; + +- (int)of_createSocketForAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + if ((_socket = socket(address->sockaddr.ipx.sipx_family, + SOCK_SEQPACKET | SOCK_CLOEXEC, NSPROTO_SPX)) == INVALID_SOCKET) { + *errNo = of_socket_errno(); + return false; + } + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + return true; +} + +- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address + errNo: (int *)errNo +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (connect(_socket, &address->sockaddr.sockaddr, + address->length) != 0) { + *errNo = of_socket_errno(); + return false; + } + + return true; +} + +- (void)of_closeSocket +{ + closesocket(_socket); + _socket = INVALID_SOCKET; +} + +- (void)connectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +{ + of_socket_address_t address = + of_socket_address_ipx(node, network, port); + int errNo; + + if (![self of_createSocketForAddress: &address + errNo: &errNo]) + @throw [OFConnectionFailedException + exceptionWithNode: node + network: network + port: port + socket: self + errNo: errNo]; + + if (![self of_connectSocketToAddress: &address + errNo: &errNo]) { + [self of_closeSocket]; + + @throw [OFConnectionFailedException + exceptionWithNode: node + network: network + port: port + socket: self + errNo: errNo]; + } +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port +{ + [self asyncConnectToNode: node + network: network + port: port + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + void *pool = objc_autoreleasePoolPush(); + + [[[[OFSPXStreamSocketAsyncConnectDelegate alloc] + initWithSocket: self + node: node + network: network + port: port +#ifdef OF_HAVE_BLOCKS + block: NULL +#endif + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + block: (of_spx_stream_socket_async_connect_block_t)block +{ + [self asyncConnectToNode: node + network: network + port: port + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncConnectToNode: (unsigned char [_Nonnull IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_spx_stream_socket_async_connect_block_t)block +{ + void *pool = objc_autoreleasePoolPush(); + + [[[[OFSPXStreamSocketAsyncConnectDelegate alloc] + initWithSocket: self + node: node + network: network + port: port + block: block + ] autorelease] startWithRunLoopMode: runLoopMode]; + + objc_autoreleasePoolPop(pool); +} +#endif + +- (of_socket_address_t)bindToPort: (uint16_t)port +{ + const unsigned char zeroNode[IPX_NODE_LEN] = { 0 }; + of_socket_address_t address; +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + int flags; +#endif + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + address = of_socket_address_ipx(zeroNode, 0, port); + + if ((_socket = socket(address.sockaddr.sockaddr.sa_family, + SOCK_STREAM | SOCK_CLOEXEC, NSPROTO_SPX)) == INVALID_SOCKET) + @throw [OFBindFailedException + exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: of_socket_errno()]; + + _canBlock = true; + +#if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL_H) && defined(FD_CLOEXEC) + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); +#endif + + if (bind(_socket, &address.sockaddr.sockaddr, address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: errNo]; + } + + memset(&address, 0, sizeof(address)); + address.family = OF_SOCKET_ADDRESS_FAMILY_IPX; + address.length = (socklen_t)sizeof(address.sockaddr); + + if (of_getsockname(_socket, &address.sockaddr.sockaddr, + &address.length) != 0) { + int errNo = of_socket_errno(); + + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: errNo]; + } + + if (address.sockaddr.sockaddr.sa_family != AF_IPX) { + closesocket(_socket); + _socket = INVALID_SOCKET; + + @throw [OFBindFailedException exceptionWithPort: port + packetType: SPX_PACKET_TYPE + socket: self + errNo: EAFNOSUPPORT]; + } + + return address; +} +@end Index: src/OFSecureData.h ================================================================== --- src/OFSecureData.h +++ src/OFSecureData.h @@ -25,12 +25,13 @@ * @brief A class for storing arbitrary data in secure (non-swappable) memory, * securely wiping it when it gets deallocated. * * @warning Non-swappable memory might be unavailable, in which case this falls * back to swappable memory, but still wipes the data when it gets - * deallocated. Check the @ref swappable property to see whether a - * particular OFSecureData was allocated in swappable memory. + * deallocated. Check the @ref allowsSwappableMemory property to see + * whether a particular OFSecureData might be allocated in swappable + * memory. */ OF_SUBCLASSING_RESTRICTED @interface OFSecureData: OFData { struct page *_page; ADDED src/OFSequencedPacketSocket+Private.h Index: src/OFSequencedPacketSocket+Private.h ================================================================== --- src/OFSequencedPacketSocket+Private.h +++ src/OFSequencedPacketSocket+Private.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFSequencedPacketSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFSequencedPacketSocket () +#ifndef OF_WII +@property (readonly, nonatomic) int of_socketError; +#endif +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSequencedPacketSocket.h Index: src/OFSequencedPacketSocket.h ================================================================== --- src/OFSequencedPacketSocket.h +++ src/OFSequencedPacketSocket.h @@ -0,0 +1,375 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFObject.h" +#import "OFKernelEventObserver.h" +#import "OFRunLoop.h" + +#import "socket.h" + +OF_ASSUME_NONNULL_BEGIN + +/*! @file */ + +@class OFData; +@class OFSequencedPacketSocket; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when a packet has been received. + * + * @param length The length of the packet + * @param exception An exception which occurred while receiving or `nil` on + * success + * @return A bool whether the same block should be used for the next receive + */ +typedef bool (^of_sequenced_packet_socket_async_receive_block_t)(size_t length, + id _Nullable exception); + +/*! + * @brief A block which is called when a packet has been sent. + * + * @param data The data which was sent + * @param exception An exception which occurred while reading or `nil` on + * success + * @return The data to repeat the send with or nil if it should not repeat + */ +typedef OFData *_Nullable (^of_sequenced_packet_socket_async_send_data_block_t)( + OFData *_Nonnull data, id _Nullable exception); + +/*! + * @brief A block which is called when the socket accepted a connection. + * + * @param acceptedSocket The socket which has been accepted + * @param exception An exception which occurred while accepting the socket or + * `nil` on success + * @return A bool whether the same block should be used for the next incoming + * connection + */ +typedef bool (^of_sequenced_packet_socket_async_accept_block_t)( + OFSequencedPacketSocket *acceptedSocket, id _Nullable exception); +#endif + +/*! + * @protocol OFSequencedPacketSocketDelegate OFSequencedPacketSocket.h \ + * ObjFW/OFSequencedPacketSocket.h + * + * @brief A delegate for OFSequencedPacketSocket. + */ +@protocol OFSequencedPacketSocketDelegate +@optional +/*! + * @brief This method is called when a packet has been received. + * + * @param socket The sequenced packet socket which received a packet + * @param buffer The buffer the packet has been written to + * @param length The length of the packet + * @param exception An exception that occurred while receiving, or nil on + * success + * @return A bool whether the same block should be used for the next receive + */ +- (bool)socket: (OFSequencedPacketSocket *)socket + didReceiveIntoBuffer: (void *)buffer + length: (size_t)length + exception: (nullable id)exception; + +/*! + * @brief This method is called when a packet has been sent. + * + * @param socket The sequenced packet socket which sent a packet + * @param data The data which was sent + * @param exception An exception that occurred while sending, or nil on success + * @return The data to repeat the send with or nil if it should not repeat + */ +- (nullable OFData *)socket: (OFSequencedPacketSocket *)socket + didSendData: (OFData *)data + exception: (nullable id)exception; + +/*! + * @brief A method which is called when a socket accepted a connection. + * + * @param socket The socket which accepted the connection + * @param acceptedSocket The socket which has been accepted + * @param exception An exception that occurred while accepting, or nil on + * success + * @return A bool whether to accept the next incoming connection + */ +- (bool)socket: (OFSequencedPacketSocket *)socket + didAcceptSocket: (OFSequencedPacketSocket *)acceptedSocket + exception: (nullable id)exception; +@end + +/*! + * @class OFSequencedPacketSocket OFSequencedPacketSocket.h \ + * ObjFW/OFSequencedPacketSocket.h + * + * @brief A base class for sequenced packet sockets. + * + * @warning Even though the OFCopying protocol is implemented, it does *not* + * return an independent copy of the socket, but instead retains it. + * This is so that the socket can be used as a key for a dictionary, + * so context can be associated with a socket. Using a socket in more + * than one thread at the same time is not thread-safe, even if copy + * was called to create one "instance" for every thread! + */ +@interface OFSequencedPacketSocket: OFObject +{ + of_socket_t _socket; + bool _canBlock, _listening; + of_socket_address_t _remoteAddress; + id _Nullable _delegate; + OF_RESERVE_IVARS(4) +} + +/*! + * @brief Whether the socket can block. + * + * By default, a socket can block. + */ +@property (nonatomic) bool canBlock; + +/*! + * @brief Whether the socket is a listening socket. + */ +@property (readonly, nonatomic, getter=isListening) bool listening; + +/*! + * @brief The remote address. + * + * @note This only works for accepted sockets! + */ +@property (readonly, nonatomic) const of_socket_address_t *remoteAddress; + +/*! + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Returns a new, autoreleased OFSequencedPacketSocket. + * + * @return A new, autoreleased OFSequencedPacketSocket + */ ++ (instancetype)socket; + +/*! + * @brief Receives a packet and stores it into the specified buffer. + * + * If the buffer is too small, the receive operation fails. + * + * @param buffer The buffer to write the packet to + * @param length The length of the buffer + * @return The length of the received packet + */ +- (size_t)receiveIntoBuffer: (void *)buffer + length: (size_t)length; + +/*! + * @brief Asynchronously receives a packet and stores it into the specified + * buffer. + * + * If the buffer is too small, the receive operation fails. + * + * @param buffer The buffer to write the packet to + * @param length The length of the buffer + */ +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length; + +/*! + * @brief Asynchronously receives a packet and stores it into the specified + * buffer. + * + * If the buffer is too small, the receive operation fails. + * + * @param buffer The buffer to write the packet to + * @param length The length of the buffer + * @param runLoopMode The run loop mode in which to perform the async receive + */ +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously receives a packet and stores it into the specified + * buffer. + * + * If the buffer is too small, the receive operation fails. + * + * @param buffer The buffer to write the packet to + * @param length The length of the buffer + * @param block The block to call when the packet has been received. If the + * block returns true, it will be called again with the same + * buffer and maximum length when more packets have been received. + * If you want the next method in the queue to handle the packet + * received next, you need to return false from the method. + */ +- (void) + asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + block: (of_sequenced_packet_socket_async_receive_block_t) + block; + +/*! + * @brief Asynchronously receives a packet and stores it into the specified + * buffer. + * + * If the buffer is too small, the receive operation fails. + * + * @param buffer The buffer to write the packet to + * @param length The length of the buffer + * @param runLoopMode The run loop mode in which to perform the async receive + * @param block The block to call when the packet has been received. If the + * block returns true, it will be called again with the same + * buffer and maximum length when more packets have been received. + * If you want the next method in the queue to handle the packet + * received next, you need to return false from the method. + */ +- (void) + asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sequenced_packet_socket_async_receive_block_t) + block; +#endif + +/*! + * @brief Sends the specified packet. + * + * @param buffer The buffer to send as a packet + * @param length The length of the buffer + */ +- (void)sendBuffer: (const void *)buffer + length: (size_t)length; + +/*! + * @brief Asynchronously sends the specified packet. + * + * @param data The data to send as a packet + */ +- (void)asyncSendData: (OFData *)data; + +/*! + * @brief Asynchronously sends the specified packet. + * + * @param data The data to send as a packet + * @param runLoopMode The run loop mode in which to perform the async send + */ +- (void)asyncSendData: (OFData *)data + runLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously sends the specified packet. + * + * @param data The data to send as a packet + * @param block The block to call when the packet has been sent. It should + * return the data for the next send with the same callback or nil + * if it should not repeat. + */ +- (void)asyncSendData: (OFData *)data + block: (of_sequenced_packet_socket_async_send_data_block_t) + block; + +/*! + * @brief Asynchronously sends the specified packet. + * + * @param data The data to send as a packet + * @param runLoopMode The run loop mode in which to perform the async send + * @param block The block to call when the packet has been sent. It should + * return the data for the next send with the same callback or nil + * if it should not repeat. + */ +- (void)asyncSendData: (OFData *)data + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sequenced_packet_socket_async_send_data_block_t) + block; +#endif + +/*! + * @brief Listen on the socket. + * + * @param backlog Maximum length for the queue of pending connections. + */ +- (void)listenWithBacklog: (int)backlog; + +/*! + * @brief Listen on the socket. + */ +- (void)listen; + +/*! + * @brief Accept an incoming connection. + * + * @return An autoreleased sequenced packet socket for the accepted connection. + */ +- (instancetype)accept; + +/*! + * @brief Asynchronously accept an incoming connection. + */ +- (void)asyncAccept; + +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param runLoopMode The run loop mode in which to perform the async accept + */ +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param block The block to execute when a new connection has been accepted. + * Returns whether the next incoming connection should be accepted + * by the specified block as well. + */ +- (void)asyncAcceptWithBlock: + (of_sequenced_packet_socket_async_accept_block_t)block; + +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param runLoopMode The run loop mode in which to perform the async accept + * @param block The block to execute when a new connection has been accepted. + * Returns whether the next incoming connection should be accepted + * by the specified block as well. + */ +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sequenced_packet_socket_async_accept_block_t)block; +#endif + +/*! + * @brief Cancels all pending asynchronous requests on the socket. + */ +- (void)cancelAsyncRequests; + +/*! + * @brief Closes the socket so that it can neither receive nor send any more + * datagrams. + */ +- (void)close; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFSequencedPacketSocket.m Index: src/OFSequencedPacketSocket.m ================================================================== --- src/OFSequencedPacketSocket.m +++ src/OFSequencedPacketSocket.m @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#ifdef HAVE_FCNTL_H +# include +#endif + +#import "OFSequencedPacketSocket.h" +#import "OFSequencedPacketSocket+Private.h" +#import "OFData.h" +#import "OFRunLoop+Private.h" +#import "OFRunLoop.h" + +#import "OFAcceptFailedException.h" +#import "OFInitializationFailedException.h" +#import "OFInvalidArgumentException.h" +#import "OFListenFailedException.h" +#import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" +#import "OFSetOptionFailedException.h" +#import "OFWriteFailedException.h" + +#import "socket.h" +#import "socket_helpers.h" + +@implementation OFSequencedPacketSocket +@synthesize listening = _listening, delegate = _delegate; + ++ (void)initialize +{ + if (self != [OFSequencedPacketSocket class]) + return; + + if (!of_socket_init()) + @throw [OFInitializationFailedException + exceptionWithClass: self]; +} + ++ (instancetype)socket +{ + return [[[self alloc] init] autorelease]; +} + +- (instancetype)init +{ + self = [super init]; + + @try { + if (self.class == [OFSequencedPacketSocket class]) { + [self doesNotRecognizeSelector: _cmd]; + abort(); + } + + _socket = INVALID_SOCKET; + _canBlock = true; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_socket != INVALID_SOCKET) + [self close]; + + [super dealloc]; +} + +#ifndef OF_WII +- (int)of_socketError +{ + int errNo; + socklen_t len = sizeof(errNo); + + if (getsockopt(_socket, SOL_SOCKET, SO_ERROR, (char *)&errNo, + &len) != 0) + return of_socket_errno(); + + return errNo; +} +#endif + +- (id)copy +{ + return [self retain]; +} + +- (bool)canBlock +{ + return _canBlock; +} + +- (void)setCanBlock: (bool)canBlock +{ +#if defined(HAVE_FCNTL) + int flags = fcntl(_socket, F_GETFL, 0); + + if (flags == -1) + @throw [OFSetOptionFailedException exceptionWithObject: self + errNo: errno]; + + if (canBlock) + flags &= ~O_NONBLOCK; + else + flags |= O_NONBLOCK; + + if (fcntl(_socket, F_SETFL, flags) == -1) + @throw [OFSetOptionFailedException exceptionWithObject: self + errNo: errno]; + + _canBlock = canBlock; +#elif defined(OF_WINDOWS) + u_long v = canBlock; + + if (ioctlsocket(_socket, FIONBIO, &v) == SOCKET_ERROR) + @throw [OFSetOptionFailedException + exceptionWithObject: self + errNo: of_socket_errno()]; + + _canBlock = canBlock; +#else + OF_UNRECOGNIZED_SELECTOR +#endif +} + +- (size_t)receiveIntoBuffer: (void *)buffer + length: (size_t)length +{ + ssize_t ret; + + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + +#ifndef OF_WINDOWS + if ((ret = recv(_socket, buffer, length, 0)) < 0) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: length + errNo: of_socket_errno()]; +#else + if (length > INT_MAX) + @throw [OFOutOfRangeException exception]; + + if ((ret = recv(_socket, buffer, (int)length, 0)) < 0) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: length + errNo: of_socket_errno()]; +#endif + + return ret; +} + +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length +{ + [self asyncReceiveIntoBuffer: buffer + length: length + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + [OFRunLoop of_addAsyncReceiveForSequencedPacketSocket: self + buffer: buffer + length: length + mode: runLoopMode +# ifdef OF_HAVE_BLOCKS + block: NULL +# endif + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void) + asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + block: (of_sequenced_packet_socket_async_receive_block_t) + block +{ + [self asyncReceiveIntoBuffer: buffer + length: length + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void) + asyncReceiveIntoBuffer: (void *)buffer + length: (size_t)length + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sequenced_packet_socket_async_receive_block_t) + block +{ + [OFRunLoop of_addAsyncReceiveForSequencedPacketSocket: self + buffer: buffer + length: length + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (void)sendBuffer: (const void *)buffer + length: (size_t)length +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + +#ifndef OF_WINDOWS + ssize_t bytesWritten; + + if (length > SSIZE_MAX) + @throw [OFOutOfRangeException exception]; + + if ((bytesWritten = send(_socket, (void *)buffer, length, 0)) < 0) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: of_socket_errno()]; +#else + int bytesWritten; + + if (length > INT_MAX) + @throw [OFOutOfRangeException exception]; + + if ((bytesWritten = send(_socket, buffer, (int)length, 0)) < 0) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: of_socket_errno()]; +#endif + + if ((size_t)bytesWritten != length) + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: bytesWritten + errNo: 0]; +} + +- (void)asyncSendData: (OFData *)data +{ + [self asyncSendData: data + runLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncSendData: (OFData *)data + runLoopMode: (of_run_loop_mode_t)runLoopMode +{ + [OFRunLoop of_addAsyncSendForSequencedPacketSocket: self + data: data + mode: runLoopMode +# ifdef OF_HAVE_BLOCKS + block: NULL +# endif + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncSendData: (OFData *)data + block: (of_sequenced_packet_socket_async_send_data_block_t)block +{ + [self asyncSendData: data + runLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncSendData: (OFData *)data + runLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sequenced_packet_socket_async_send_data_block_t)block +{ + [OFRunLoop of_addAsyncSendForSequencedPacketSocket: self + data: data + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (void)listen +{ + [self listenWithBacklog: SOMAXCONN]; +} + +- (void)listenWithBacklog: (int)backlog +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (listen(_socket, backlog) == -1) + @throw [OFListenFailedException + exceptionWithSocket: self + backlog: backlog + errNo: of_socket_errno()]; + + _listening = true; +} + +- (instancetype)accept +{ + OFSequencedPacketSocket *client = + [[[[self class] alloc] init] autorelease]; +#if (!defined(HAVE_PACCEPT) && !defined(HAVE_ACCEPT4)) || !defined(SOCK_CLOEXEC) +# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +# endif +#endif + + client->_remoteAddress.length = + (socklen_t)sizeof(client->_remoteAddress.sockaddr); + +#if defined(HAVE_PACCEPT) && defined(SOCK_CLOEXEC) + if ((client->_socket = paccept(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length, NULL, SOCK_CLOEXEC)) == + INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; +#elif defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) + if ((client->_socket = accept4(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length, SOCK_CLOEXEC)) == INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; +#else + if ((client->_socket = accept(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length)) == INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; + +# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(client->_socket, F_GETFD, 0)) != -1) + fcntl(client->_socket, F_SETFD, flags | FD_CLOEXEC); +# endif +#endif + + assert(client->_remoteAddress.length <= + (socklen_t)sizeof(client->_remoteAddress.sockaddr)); + + switch (client->_remoteAddress.sockaddr.sockaddr.sa_family) { + case AF_INET: + client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV4; + break; +#ifdef OF_HAVE_IPV6 + case AF_INET6: + client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV6; + break; +#endif +#ifdef OF_HAVE_IPX + case AF_IPX: + client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPX; + break; +#endif + default: + client->_remoteAddress.family = + OF_SOCKET_ADDRESS_FAMILY_UNKNOWN; + break; + } + + return client; +} + +- (void)asyncAccept +{ + [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + [OFRunLoop of_addAsyncAcceptForSocket: self + mode: runLoopMode + block: NULL + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncAcceptWithBlock: (of_sequenced_packet_socket_async_accept_block_t) + block +{ + [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_sequenced_packet_socket_async_accept_block_t)block +{ + [OFRunLoop of_addAsyncAcceptForSocket: self + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (const of_socket_address_t *)remoteAddress +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_remoteAddress.length == 0) + @throw [OFInvalidArgumentException exception]; + + if (_remoteAddress.length > (socklen_t)sizeof(_remoteAddress.sockaddr)) + @throw [OFOutOfRangeException exception]; + + return &_remoteAddress; +} + +- (void)cancelAsyncRequests +{ + [OFRunLoop of_cancelAsyncRequestsForObject: self + mode: of_run_loop_mode_default]; +} + +- (int)fileDescriptorForReading +{ +#ifndef OF_WINDOWS + return _socket; +#else + if (_socket == INVALID_SOCKET) + return -1; + + if (_socket > INT_MAX) + @throw [OFOutOfRangeException exception]; + + return (int)_socket; +#endif +} + +- (int)fileDescriptorForWriting +{ +#ifndef OF_WINDOWS + return _socket; +#else + if (_socket == INVALID_SOCKET) + return -1; + + if (_socket > INT_MAX) + @throw [OFOutOfRangeException exception]; + + return (int)_socket; +#endif +} + +- (void)close +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + _listening = false; + memset(&_remoteAddress, 0, sizeof(_remoteAddress)); + + closesocket(_socket); + _socket = INVALID_SOCKET; +} +@end Index: src/OFStdIOStream.h ================================================================== --- src/OFStdIOStream.h +++ src/OFStdIOStream.h @@ -22,10 +22,14 @@ # include #endif OF_ASSUME_NONNULL_BEGIN +/*! @file */ + +@class OFColor; + /*! * @class OFStdIOStream OFStdIOStream.h ObjFW/OFStdIOStream.h * * @brief A class for providing standard input, output and error as OFStream. * @@ -47,10 +51,15 @@ bool _closable; #endif bool _atEndOfStream; } +/*! + * @brief Whether there is an underlying terminal. + */ +@property (readonly, nonatomic) bool hasTerminal; + /*! * @brief The number of columns, or -1 if there is no underlying terminal or * the number of columns could not be queried. */ @property (readonly, nonatomic) int columns; @@ -60,10 +69,68 @@ * number of rows could not be queried. */ @property (readonly, nonatomic) int rows; - (instancetype)init OF_UNAVAILABLE; + +/*! + * @brief Sets the foreground color on the underlying terminal. Does nothing if + * there is no underlying terminal or colors are unsupported. + * + * @param color The foreground color to set + */ +- (void)setForegroundColor: (OFColor *)color; + +/*! + * @brief Sets the background color on the underlying terminal. Does nothing if + * there is no underlying terminal or colors are unsupported. + * + * @param color The background color to set + */ +- (void)setBackgroundColor: (OFColor *)color; + +/*! + * @brief Resets all attributes (color, bold, etc.). Does nothing if there is + * no underlying terminal. + */ +- (void)reset; + +/*! + * @brief Clears the entire underlying terminal. Does nothing if there is no + * underlying terminal. + */ +- (void)clear; + +/*! + * @brief Erases the entire current line on the underlying terminal. Does + * nothing if there is no underlying terminal. + */ +- (void)eraseLine; + +/*! + * @brief Moves the cursor to the specified column in the current row. Does + * nothing if there is no underlying terminal. + * + * @param column The column in the current row to move the cursor to + */ +- (void)setCursorColumn: (unsigned int)column; + +/*! + * @brief Moves the cursor to the specified absolute position. Does nothing if + * there is no underlying terminal. + * + * @param position The position to move the cursor to + */ +- (void)setCursorPosition: (of_point_t)position; + +/*! + * @brief Moves the cursor to the specified relative position. Does nothing if + * there is no underlying terminal. + * + * @param position The position to move the cursor to + */ +- (void)setRelativeCursorPosition: (of_point_t)position; @end #ifdef __cplusplus extern "C" { #endif @@ -82,11 +149,17 @@ /*! * @brief The standard error as an OFStream. */ extern OFStdIOStream *_Nullable of_stderr; -extern void of_log(OFConstantString *, ...); +/*! + * @brief Log the specified printf-style format to @ref of_stderr. + * + * This prefixes the output with the date, timestamp, process name and PID and + * allows `%@` as a printf-style formatted to print objects. + */ +extern void of_log(OFConstantString *format, ...); #ifdef __cplusplus } #endif OF_ASSUME_NONNULL_END Index: src/OFStdIOStream.m ================================================================== --- src/OFStdIOStream.m +++ src/OFStdIOStream.m @@ -28,17 +28,19 @@ # include #endif #import "OFStdIOStream.h" #import "OFStdIOStream+Private.h" +#import "OFColor.h" #import "OFDate.h" #import "OFApplication.h" #ifdef OF_WINDOWS # include "OFWin32ConsoleStdIOStream.h" #endif #import "OFInitializationFailedException.h" +#import "OFInvalidArgumentException.h" #import "OFNotOpenException.h" #import "OFOutOfRangeException.h" #import "OFReadFailedException.h" #import "OFWriteFailedException.h" @@ -93,10 +95,51 @@ [of_stderr writeFormat: @"[%@.%03d %@(%d)] %@\n", dateString, date.microsecond / 1000, me, getpid(), msg]; objc_autoreleasePoolPop(pool); } + +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) +static int +colorToANSI(OFColor *color) +{ + if ([color isEqual: [OFColor black]]) + return 30; + if ([color isEqual: [OFColor maroon]]) + return 31; + if ([color isEqual: [OFColor green]]) + return 32; + if ([color isEqual: [OFColor olive]]) + return 33; + if ([color isEqual: [OFColor navy]]) + return 34; + if ([color isEqual: [OFColor purple]]) + return 35; + if ([color isEqual: [OFColor teal]]) + return 36; + if ([color isEqual: [OFColor silver]]) + return 37; + if ([color isEqual: [OFColor grey]]) + return 90; + if ([color isEqual: [OFColor red]]) + return 91; + if ([color isEqual: [OFColor lime]]) + return 92; + if ([color isEqual: [OFColor yellow]]) + return 93; + if ([color isEqual: [OFColor blue]]) + return 94; + if ([color isEqual: [OFColor fuchsia]]) + return 95; + if ([color isEqual: [OFColor aqua]]) + return 96; + if ([color isEqual: [OFColor white]]) + return 97; + + return -1; +} +#endif @implementation OFStdIOStream #ifndef OF_WINDOWS + (void)load { @@ -340,10 +383,19 @@ - (unsigned int)retainCount { return OF_RETAIN_COUNT_MAX; } + +- (bool)hasTerminal +{ +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + return isatty(_fd); +#else + return false; +#endif +} - (int)columns { #if defined(HAVE_SYS_IOCTL_H) && defined(TIOCGWINSZ) && !defined(OF_AMIGAOS) struct winsize ws; @@ -368,6 +420,108 @@ return ws.ws_row; #else return -1; #endif } + +- (void)setForegroundColor: (OFColor *)color +{ +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + int code; + + if (!isatty(_fd)) + return; + + if ((code = colorToANSI(color)) == -1) + return; + + [self writeFormat: @"\033[%um", code]; +#endif +} + +- (void)setBackgroundColor: (OFColor *)color +{ +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + int code; + + if (!isatty(_fd)) + return; + + if ((code = colorToANSI(color)) == -1) + return; + + [self writeFormat: @"\033[%um", code + 10]; +#endif +} + +- (void)reset +{ +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + if (!isatty(_fd)) + return; + + [self writeString: @"\033[0m"]; +#endif +} + +- (void)clear +{ +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + if (!isatty(_fd)) + return; + + [self writeString: @"\033[2J"]; +#endif +} + +- (void)eraseLine +{ +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + if (!isatty(_fd)) + return; + + [self writeString: @"\033[2K"]; +#endif +} + +- (void)setCursorColumn: (unsigned int)column +{ +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + if (!isatty(_fd)) + return; + + [self writeFormat: @"\033[%uG", column + 1]; +#endif +} + +- (void)setCursorPosition: (of_point_t)position +{ + if (position.x < 0 || position.y < 0) + @throw [OFInvalidArgumentException exception]; + +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + if (!isatty(_fd)) + return; + + [self writeFormat: @"\033[%u;%uH", + (unsigned)position.y + 1, (unsigned)position.x + 1]; +#endif +} + +- (void)setRelativeCursorPosition: (of_point_t)position +{ +#if defined(HAVE_ISATTY) && !defined(OF_AMIGAOS) + if (!isatty(_fd)) + return; + + if (position.x > 0) + [self writeFormat: @"\033[%uC", (unsigned)position.x]; + else if (position.x < 0) + [self writeFormat: @"\033[%uD", (unsigned)-position.x]; + + if (position.y > 0) + [self writeFormat: @"\033[%uB", (unsigned)position.y]; + else if (position.y < 0) + [self writeFormat: @"\033[%uA", (unsigned)-position.y]; +#endif +} @end Index: src/OFStream.h ================================================================== --- src/OFStream.h +++ src/OFStream.h @@ -41,33 +41,30 @@ #if defined(OF_HAVE_SOCKETS) && defined(OF_HAVE_BLOCKS) /*! * @brief A block which is called when data was read asynchronously from a * stream. * - * @param stream The stream on which data was read - * @param buffer A buffer with the data that has been read * @param length The length of the data that has been read * @param exception An exception which occurred while reading or `nil` on * success * @return A bool whether the same block should be used for the next read */ -typedef bool (^of_stream_async_read_block_t)(OFStream *_Nonnull stream, - void *_Nonnull buffer, size_t length, id _Nullable exception); +typedef bool (^of_stream_async_read_block_t)(size_t length, + id _Nullable exception); /*! * @brief A block which is called when a line was read asynchronously from a * stream. * - * @param stream The stream on which a line was read * @param line The line which has been read or `nil` when the end of stream * occurred * @param exception An exception which occurred while reading or `nil` on * success * @return A bool whether the same block should be used for the next read */ -typedef bool (^of_stream_async_read_line_block_t)(OFStream *_Nonnull stream, - OFString *_Nullable line, id _Nullable exception); +typedef bool (^of_stream_async_read_line_block_t)(OFString *_Nullable line, + id _Nullable exception); /*! * @brief A block which is called when data was written asynchronously to a * stream. * @@ -78,29 +75,26 @@ * @param exception An exception which occurred while writing or `nil` on * success * @return The data to repeat the write with or nil if it should not repeat */ typedef OFData *_Nullable (^of_stream_async_write_data_block_t)( - OFStream *_Nonnull stream, OFData *_Nonnull data, - size_t bytesWritten, id _Nullable exception); + OFData *_Nonnull data, size_t bytesWritten, id _Nullable exception); /*! * @brief A block which is called when a string was written asynchronously to a * stream. * * @param string The string which was written to the stream * @param bytesWritten The number of bytes which have been written. This * matches the length of the specified data on the * asynchronous write if no exception was encountered. - * @param encoding The encoding in which the string was written * @param exception An exception which occurred while writing or `nil` on * success * @return The string to repeat the write with or nil if it should not repeat */ typedef OFString *_Nullable (^of_stream_async_write_string_block_t)( - OFStream *_Nonnull stream, OFString *_Nonnull string, - of_string_encoding_t encoding, size_t bytesWritten, id _Nullable exception); + OFString *_Nonnull string, size_t bytesWritten, id _Nullable exception); #endif /*! * @protocol OFStreamDelegate OFStream.h ObjFW/OFStream.h * @@ -194,19 +188,19 @@ * override these methods without the `lowlevel` prefix, you *will* break * caching and get broken results! */ @interface OFStream: OFObject { - bool _blocking; + bool _canBlock; id _Nullable _delegate; -#if !defined(OF_SEEKABLE_STREAM_M) && !defined(OF_TCP_SOCKET_M) +#ifndef OF_SEEKABLE_STREAM_M @private #endif char *_Nullable _readBuffer, *_Nullable _readBufferMemory; char *_Nullable _writeBuffer; size_t _readBufferLength, _writeBufferLength; - bool _writeBuffered, _waitingForDelimiter; + bool _buffersWrites, _waitingForDelimiter; OF_RESERVE_IVARS(4) } /*! * @brief Whether the end of the stream has been reached. @@ -214,30 +208,30 @@ @property (readonly, nonatomic, getter=isAtEndOfStream) bool atEndOfStream; /*! * @brief Whether writes are buffered. */ -@property (nonatomic, nonatomic, getter=isWriteBuffered) bool writeBuffered; +@property (nonatomic, nonatomic) bool buffersWrites; /*! * @brief Whether data is present in the internal read buffer. */ @property (readonly, nonatomic) bool hasDataInReadBuffer; /*! - * @brief Whether the stream is in blocking mode. + * @brief Whether the stream can block. * - * By default, a stream is in blocking mode. + * By default, a stream can block. * On Win32, setting this currently only works for sockets! */ -@property (nonatomic, getter=isBlocking) bool blocking; +@property (nonatomic) bool canBlock; /*! * @brief The delegate for asynchronous operations on the stream. * * @note The delegate is retained for as long as asynchronous operations are - * still outstanding. + * still ongoing. */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) id delegate; /*! @@ -245,11 +239,13 @@ * * On network streams, this might read less than the specified number of bytes. * If you want to read exactly the specified number of bytes, use * @ref readIntoBuffer:exactLength:. Note that a read can even return 0 bytes - * this does not necessarily mean that the stream ended, so you still need to - * check @ref atEndOfStream. + * check @ref atEndOfStream. Do not assume that the stream ended just because a + * read returned 0 bytes - some streams do internal processing that has a + * result of 0 bytes. * * @param buffer The buffer into which the data is read * @param length The length of the data that should be read at most. * The buffer *must* be *at least* this big! * @return The number of bytes read @@ -282,11 +278,13 @@ * * On network streams, this might read less than the specified number of bytes. * If you want to read exactly the specified number of bytes, use * @ref asyncReadIntoBuffer:exactLength:. Note that a read can even return 0 * bytes - this does not necessarily mean that the stream ended, so you still - * need to check @ref atEndOfStream. + * need to check @ref atEndOfStream. Do not assume that the stream ended just + * because a read returned 0 bytes - some streams do internal processing that + * has a result of 0 bytes. * * @note The stream must conform to @ref OFReadyForReadingObserving in order * for this to work! * * @param buffer The buffer into which the data is read. @@ -303,11 +301,13 @@ * * On network streams, this might read less than the specified number of bytes. * If you want to read exactly the specified number of bytes, use * @ref asyncReadIntoBuffer:exactLength:. Note that a read can even return 0 * bytes - this does not necessarily mean that the stream ended, so you still - * need to check @ref atEndOfStream. + * need to check @ref atEndOfStream. Do not assume that the stream ended just + * because a read returned 0 bytes - some streams do internal processing that + * has a result of 0 bytes. * * @note The stream must conform to @ref OFReadyForReadingObserving in order * for this to work! * * @param buffer The buffer into which the data is read. @@ -367,11 +367,13 @@ * * On network streams, this might read less than the specified number of bytes. * If you want to read exactly the specified number of bytes, use * @ref asyncReadIntoBuffer:exactLength:block:. Note that a read can even * return 0 bytes - this does not necessarily mean that the stream ended, so - * you still need to check @ref atEndOfStream. + * you still need to check @ref atEndOfStream. Do not assume that the stream + * ended just because a read returned 0 bytes - some streams do internal + * processing that has a result of 0 bytes. * * @note The stream must conform to @ref OFReadyForReadingObserving in order * for this to work! * * @param buffer The buffer into which the data is read. @@ -394,11 +396,13 @@ * * On network streams, this might read less than the specified number of bytes. * If you want to read exactly the specified number of bytes, use * @ref asyncReadIntoBuffer:exactLength:block:. Note that a read can even * return 0 bytes - this does not necessarily mean that the stream ended, so - * you still need to check @ref atEndOfStream. + * you still need to check @ref atEndOfStream. Do not assume that the stream + * ended just because a read returned 0 bytes - some streams do internal + * processing that has a result of 0 bytes. * * @note The stream must conform to @ref OFReadyForReadingObserving in order * for this to work! * * @param buffer The buffer into which the data is read. Index: src/OFStream.m ================================================================== --- src/OFStream.m +++ src/OFStream.m @@ -61,10 +61,11 @@ #import "of_asprintf.h" #define MIN_READ_SIZE 512 @implementation OFStream +@synthesize buffersWrites = _buffersWrites; @synthesize of_waitingForDelimiter = _waitingForDelimiter, delegate = _delegate; #if defined(SIGPIPE) && defined(SIG_IGN) + (void)initialize { @@ -73,23 +74,23 @@ } #endif - (instancetype)init { - if ([self isMemberOfClass: [OFStream class]]) { - @try { + self = [super init]; + + @try { + if (self.class == [OFStream class]) { [self doesNotRecognizeSelector: _cmd]; abort(); - } @catch (id e) { - [self release]; - @throw e; - } - } - - self = [super init]; - - _blocking = true; + } + + _canBlock = true; + } @catch (id e) { + [self release]; + @throw e; + } return self; } - (bool)lowlevelIsAtEndOfStream @@ -1126,20 +1127,10 @@ { return [self tryReadTillDelimiter: delimiter encoding: OF_STRING_ENCODING_UTF_8]; } -- (bool)isWriteBuffered -{ - return _writeBuffered; -} - -- (void)setWriteBuffered: (bool)enable -{ - _writeBuffered = enable; -} - - (void)flushWriteBuffer { if (_writeBuffer == NULL) return; @@ -1152,15 +1143,15 @@ } - (size_t)writeBuffer: (const void *)buffer length: (size_t)length { - if (!_writeBuffered) { + if (!_buffersWrites) { size_t bytesWritten = [self lowlevelWriteBuffer: buffer length: length]; - if (_blocking && bytesWritten < length) + if (_canBlock && bytesWritten < length) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: length bytesWritten: bytesWritten errNo: 0]; @@ -1798,16 +1789,16 @@ - (bool)hasDataInReadBuffer { return (_readBufferLength > 0); } -- (bool)isBlocking +- (bool)canBlock { - return _blocking; + return _canBlock; } -- (void)setBlocking: (bool)enable +- (void)setCanBlock: (bool)canBlock { #if defined(HAVE_FCNTL) && !defined(OF_AMIGAOS) bool readImplemented = false, writeImplemented = false; @try { @@ -1821,11 +1812,11 @@ if (readFlags == -1) @throw [OFSetOptionFailedException exceptionWithObject: self errNo: errno]; - if (enable) + if (canBlock) readFlags &= ~O_NONBLOCK; else readFlags |= O_NONBLOCK; if (fcntl(((id )self) @@ -1847,11 +1838,11 @@ if (writeFlags == -1) @throw [OFSetOptionFailedException exceptionWithObject: self errNo: errno]; - if (enable) + if (canBlock) writeFlags &= ~O_NONBLOCK; else writeFlags |= O_NONBLOCK; if (fcntl(((id )self) @@ -1864,11 +1855,11 @@ if (!readImplemented && !writeImplemented) @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; - _blocking = enable; + _canBlock = canBlock; #else OF_UNRECOGNIZED_SELECTOR #endif } @@ -1914,10 +1905,10 @@ _readBufferLength = 0; [self freeMemory: _writeBuffer]; _writeBuffer = NULL; _writeBufferLength = 0; - _writeBuffered = false; + _buffersWrites = false; _waitingForDelimiter = false; } @end ADDED src/OFStreamSocket+Private.h Index: src/OFStreamSocket+Private.h ================================================================== --- src/OFStreamSocket+Private.h +++ src/OFStreamSocket+Private.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFStreamSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@interface OFStreamSocket () +#ifndef OF_WII +@property (readonly, nonatomic) int of_socketError; +#endif +@end + +OF_ASSUME_NONNULL_END Index: src/OFStreamSocket.h ================================================================== --- src/OFStreamSocket.h +++ src/OFStreamSocket.h @@ -19,27 +19,142 @@ #import "socket.h" OF_ASSUME_NONNULL_BEGIN +/*! @file */ + +@class OFStreamSocket; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief A block which is called when the socket accepted a connection. + * + * @param acceptedSocket The socket which has been accepted + * @param exception An exception which occurred while accepting the socket or + * `nil` on success + * @return A bool whether the same block should be used for the next incoming + * connection + */ +typedef bool (^of_stream_socket_async_accept_block_t)( + OFStreamSocket *acceptedSocket, id _Nullable exception); +#endif + +/*! + * @protocol OFStreamSocketDelegate OFStreamSocket.h ObjFW/OFStreamSocket.h + * + * A delegate for OFStreamSocket. + */ +@protocol OFStreamSocketDelegate +@optional +/*! + * @brief A method which is called when a socket accepted a connection. + * + * @param socket The socket which accepted the connection + * @param acceptedSocket The socket which has been accepted + * @param exception An exception that occurred while accepting, or nil on + * success + * @return A bool whether to accept the next incoming connection + */ +- (bool)socket: (OFStreamSocket *)socket + didAcceptSocket: (OFStreamSocket *)acceptedSocket + exception: (nullable id)exception; +@end + /*! * @class OFStreamSocket OFStreamSocket.h ObjFW/OFStreamSocket.h * * @brief A class which provides methods to create and use stream sockets. */ @interface OFStreamSocket: OFStream { of_socket_t _socket; - bool _atEndOfStream; + bool _atEndOfStream, _listening; + of_socket_address_t _remoteAddress; OF_RESERVE_IVARS(4) } /*! - * @brief Returns a new, autoreleased OFTCPSocket. + * @brief Whether the socket is a listening socket. + */ +@property (readonly, nonatomic, getter=isListening) bool listening; + +/*! + * @brief The remote address. + * + * @note This only works for accepted sockets! + */ +@property (readonly, nonatomic) const of_socket_address_t *remoteAddress; + +/*! + * @brief The delegate for asynchronous operations on the socket. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/*! + * @brief Returns a new, autoreleased OFStreamSocket. * - * @return A new, autoreleased OFTCPSocket + * @return A new, autoreleased OFStreamSocket */ + (instancetype)socket; + +/*! + * @brief Listen on the socket. + * + * @param backlog Maximum length for the queue of pending connections. + */ +- (void)listenWithBacklog: (int)backlog; + +/*! + * @brief Listen on the socket. + */ +- (void)listen; + +/*! + * @brief Accept an incoming connection. + * + * @return An autoreleased OFStreamSocket for the accepted connection. + */ +- (instancetype)accept; + +/*! + * @brief Asynchronously accept an incoming connection. + */ +- (void)asyncAccept; + +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param runLoopMode The run loop mode in which to perform the async accept + */ +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; + +#ifdef OF_HAVE_BLOCKS +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param block The block to execute when a new connection has been accepted. + * Returns whether the next incoming connection should be accepted + * by the specified block as well. + */ +- (void)asyncAcceptWithBlock: (of_stream_socket_async_accept_block_t)block; + +/*! + * @brief Asynchronously accept an incoming connection. + * + * @param runLoopMode The run loop mode in which to perform the async accept + * @param block The block to execute when a new connection has been accepted. + * Returns whether the next incoming connection should be accepted + * by the specified block as well. + */ +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_stream_socket_async_accept_block_t) + block; +#endif @end OF_ASSUME_NONNULL_END Index: src/OFStreamSocket.m ================================================================== --- src/OFStreamSocket.m +++ src/OFStreamSocket.m @@ -17,16 +17,23 @@ #define __NO_EXT_QNX #include "config.h" +#include #include #include #import "OFStreamSocket.h" +#import "OFStreamSocket+Private.h" +#import "OFRunLoop.h" +#import "OFRunLoop+Private.h" +#import "OFAcceptFailedException.h" #import "OFInitializationFailedException.h" +#import "OFInvalidArgumentException.h" +#import "OFListenFailedException.h" #import "OFNotImplementedException.h" #import "OFNotOpenException.h" #import "OFOutOfRangeException.h" #import "OFReadFailedException.h" #import "OFSetOptionFailedException.h" @@ -33,10 +40,13 @@ #import "OFWriteFailedException.h" #import "socket_helpers.h" @implementation OFStreamSocket +@dynamic delegate; +@synthesize listening = _listening; + + (void)initialize { if (self != [OFStreamSocket class]) return; @@ -47,10 +57,37 @@ + (instancetype)socket { return [[[self alloc] init] autorelease]; } + +- (instancetype)init +{ + self = [super init]; + + @try { + if (self.class == [OFStreamSocket class]) { + [self doesNotRecognizeSelector: _cmd]; + abort(); + } + + _socket = INVALID_SOCKET; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_socket != INVALID_SOCKET) + [self close]; + + [super dealloc]; +} - (bool)lowlevelIsAtEndOfStream { if (_socket == INVALID_SOCKET) @throw [OFNotOpenException exceptionWithObject: self]; @@ -71,14 +108,14 @@ @throw [OFReadFailedException exceptionWithObject: self requestedLength: length errNo: of_socket_errno()]; #else - if (length > UINT_MAX) + if (length > INT_MAX) @throw [OFOutOfRangeException exception]; - if ((ret = recv(_socket, buffer, (unsigned int)length, 0)) < 0) + if ((ret = recv(_socket, buffer, (int)length, 0)) < 0) @throw [OFReadFailedException exceptionWithObject: self requestedLength: length errNo: of_socket_errno()]; #endif @@ -123,24 +160,24 @@ return (size_t)bytesWritten; } #if defined(OF_WINDOWS) || defined(OF_AMIGAOS) -- (void)setBlocking: (bool)enable +- (void)setCanBlock: (bool)canBlock { # ifdef OF_WINDOWS - u_long v = enable; + u_long v = canBlock; # else - char v = enable; + char v = canBlock; # endif if (ioctlsocket(_socket, FIONBIO, &v) == SOCKET_ERROR) @throw [OFSetOptionFailedException exceptionWithObject: self errNo: of_socket_errno()]; - _blocking = enable; + _canBlock = canBlock; } #endif - (int)fileDescriptorForReading { @@ -169,27 +206,166 @@ @throw [OFOutOfRangeException exception]; return (int)_socket; #endif } + +#ifndef OF_WII +- (int)of_socketError +{ + int errNo; + socklen_t len = sizeof(errNo); + + if (getsockopt(_socket, SOL_SOCKET, SO_ERROR, (char *)&errNo, + &len) != 0) + return of_socket_errno(); + + return errNo; +} +#endif + +- (void)listen +{ + [self listenWithBacklog: SOMAXCONN]; +} + +- (void)listenWithBacklog: (int)backlog +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (listen(_socket, backlog) == -1) + @throw [OFListenFailedException + exceptionWithSocket: self + backlog: backlog + errNo: of_socket_errno()]; + + _listening = true; +} + +- (instancetype)accept +{ + OFStreamSocket *client = [[[[self class] alloc] init] autorelease]; +#if (!defined(HAVE_PACCEPT) && !defined(HAVE_ACCEPT4)) || !defined(SOCK_CLOEXEC) +# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + int flags; +# endif +#endif + + client->_remoteAddress.length = + (socklen_t)sizeof(client->_remoteAddress.sockaddr); + +#if defined(HAVE_PACCEPT) && defined(SOCK_CLOEXEC) + if ((client->_socket = paccept(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length, NULL, SOCK_CLOEXEC)) == + INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; +#elif defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) + if ((client->_socket = accept4(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length, SOCK_CLOEXEC)) == INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; +#else + if ((client->_socket = accept(_socket, + &client->_remoteAddress.sockaddr.sockaddr, + &client->_remoteAddress.length)) == INVALID_SOCKET) + @throw [OFAcceptFailedException + exceptionWithSocket: self + errNo: of_socket_errno()]; + +# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) + if ((flags = fcntl(client->_socket, F_GETFD, 0)) != -1) + fcntl(client->_socket, F_SETFD, flags | FD_CLOEXEC); +# endif +#endif + + assert(client->_remoteAddress.length <= + (socklen_t)sizeof(client->_remoteAddress.sockaddr)); + + switch (client->_remoteAddress.sockaddr.sockaddr.sa_family) { + case AF_INET: + client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV4; + break; +#ifdef OF_HAVE_IPV6 + case AF_INET6: + client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV6; + break; +#endif +#ifdef OF_HAVE_IPX + case AF_IPX: + client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPX; + break; +#endif + default: + client->_remoteAddress.family = + OF_SOCKET_ADDRESS_FAMILY_UNKNOWN; + break; + } + + return client; +} + +- (void)asyncAccept +{ + [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default]; +} + +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode +{ + [OFRunLoop of_addAsyncAcceptForSocket: self + mode: runLoopMode + block: NULL + delegate: _delegate]; +} + +#ifdef OF_HAVE_BLOCKS +- (void)asyncAcceptWithBlock: (of_stream_socket_async_accept_block_t)block +{ + [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default + block: block]; +} + +- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode + block: (of_stream_socket_async_accept_block_t)block +{ + [OFRunLoop of_addAsyncAcceptForSocket: self + mode: runLoopMode + block: block + delegate: nil]; +} +#endif + +- (const of_socket_address_t *)remoteAddress +{ + if (_socket == INVALID_SOCKET) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_remoteAddress.length == 0) + @throw [OFInvalidArgumentException exception]; + + if (_remoteAddress.length > (socklen_t)sizeof(_remoteAddress.sockaddr)) + @throw [OFOutOfRangeException exception]; + + return &_remoteAddress; +} - (void)close { if (_socket == INVALID_SOCKET) @throw [OFNotOpenException exceptionWithObject: self]; + _listening = false; + memset(&_remoteAddress, 0, sizeof(_remoteAddress)); + closesocket(_socket); _socket = INVALID_SOCKET; _atEndOfStream = false; [super close]; } - -- (void)dealloc -{ - if (_socket != INVALID_SOCKET) - [self close]; - - [super dealloc]; -} @end Index: src/OFString.h ================================================================== --- src/OFString.h +++ src/OFString.h @@ -12,19 +12,22 @@ * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ + +#ifndef OBJFW_OF_STRING_H +#define OBJFW_OF_STRING_H #ifndef __STDC_LIMIT_MACROS # define __STDC_LIMIT_MACROS #endif #ifndef __STDC_CONSTANT_MACROS # define __STDC_CONSTANT_MACROS #endif -#import "objfw-defs.h" +#include "objfw-defs.h" #ifdef OF_HAVE_SYS_TYPES_H # include #endif @@ -32,20 +35,27 @@ #include #ifdef OF_HAVE_INTTYPES_H # include #endif -#import "OFObject.h" -#import "OFSerialization.h" -#import "OFJSONRepresentation.h" -#import "OFMessagePackRepresentation.h" +#include "OFObject.h" +#ifdef __OBJC__ +# import "OFSerialization.h" +# import "OFJSONRepresentation.h" +# import "OFMessagePackRepresentation.h" +#endif OF_ASSUME_NONNULL_BEGIN /*! @file */ +#ifdef __OBJC__ @class OFConstantString; +@class OFString; +#else +typedef void OFString; +#endif #if defined(__cplusplus) && __cplusplus >= 201103L typedef char16_t of_char16_t; typedef char32_t of_char32_t; #else @@ -108,10 +118,11 @@ * enumeration */ typedef void (^of_string_line_enumeration_block_t)(OFString *line, bool *stop); #endif +#ifdef __OBJC__ @class OFArray OF_GENERIC(ObjectType); @class OFCharacterSet; @class OFURL; /*! @@ -163,11 +174,11 @@ * @brief The decimal value of the string as an `intmax_t`. * * Leading and trailing whitespaces are ignored. * * If the string contains any non-number characters, an - * @ref OFInvalidEncodingException is thrown. + * @ref OFInvalidFormatException is thrown. * * If the number is too big to fit into an `intmax_t`, an * @ref OFOutOfRangeException is thrown. */ @property (readonly, nonatomic) intmax_t decimalValue; @@ -176,11 +187,11 @@ * @brief The hexadecimal value of the string as an `uintmax_t`. * * Leading and trailing whitespaces are ignored. * * If the string contains any non-number characters, an - * @ref OFInvalidEncodingException is thrown. + * @ref OFInvalidFormatException is thrown. * * If the number is too big to fit into an `uintmax_t`, an * @ref OFOutOfRangeException is thrown. */ @property (readonly, nonatomic) uintmax_t hexadecimalValue; @@ -189,11 +200,11 @@ * @brief The octal value of the string as an `uintmax_t`. * * Leading and trailing whitespaces are ignored. * * If the string contains any non-number characters, an - * @ref OFInvalidEncodingException is thrown. + * @ref OFInvalidFormatException is thrown. * * If the number is too big to fit into an `uintmax_t`, an * @ref OFOutOfRangeException is thrown. */ @property (readonly, nonatomic) uintmax_t octalValue; @@ -200,19 +211,19 @@ /*! * @brief The float value of the string as a float. * * If the string contains any non-number characters, an - * @ref OFInvalidEncodingException is thrown. + * @ref OFInvalidFormatException is thrown. */ @property (readonly, nonatomic) float floatValue; /*! * @brief The double value of the string as a double. * * If the string contains any non-number characters, an - * OFInvalidEncodingException is thrown. + * @ref OFInvalidFormatException is thrown. */ @property (readonly, nonatomic) double doubleValue; /*! * @brief The string as an array of Unicode characters. @@ -262,11 +273,11 @@ /*! * @brief The string with leading and trailing whitespaces deleted. */ @property (readonly, nonatomic) OFString *stringByDeletingEnclosingWhitespaces; -#ifdef OF_HAVE_UNICODE_TABLES +# ifdef OF_HAVE_UNICODE_TABLES /*! * @brief The string in Unicode Normalization Form D (NFD). */ @property (readonly, nonatomic) OFString *decomposedStringWithCanonicalMapping; @@ -273,11 +284,19 @@ /*! * @brief The string in Unicode Normalization Form KD (NFKD). */ @property (readonly, nonatomic) OFString *decomposedStringWithCompatibilityMapping; -#endif +# endif + +# ifdef OF_WINDOWS +/*! + * @brief The string with the Windows Environment Strings expanded. + */ +@property (readonly, nonatomic) + OFString *stringByExpandingWindowsEnvironmentStrings; +# endif /*! * @brief Creates a new OFString. * * @return A new, autoreleased OFString @@ -489,11 +508,11 @@ * @param format A string used as format to initialize the OFString * @return A new autoreleased OFString */ + (instancetype)stringWithFormat: (OFConstantString *)format, ...; -#ifdef OF_HAVE_FILES +# ifdef OF_HAVE_FILES /*! * @brief Creates a new OFString with the contents of the specified UTF-8 * encoded file. * * @param path The path to the file @@ -509,13 +528,13 @@ * @param encoding The encoding of the file * @return A new autoreleased OFString */ + (instancetype)stringWithContentsOfFile: (OFString *)path encoding: (of_string_encoding_t)encoding; -#endif +# endif -#if defined(OF_HAVE_FILES) || defined(OF_HAVE_SOCKETS) +# if defined(OF_HAVE_FILES) || defined(OF_HAVE_SOCKETS) /*! * @brief Creates a new OFString with the contents of the specified URL. * * If the URL's scheme is file, it tries UTF-8 encoding. * @@ -536,11 +555,11 @@ * @param encoding The encoding to assume * @return A new autoreleased OFString */ + (instancetype)stringWithContentsOfURL: (OFURL *)URL encoding: (of_string_encoding_t)encoding; -#endif +# endif /*! * @brief Initializes an already allocated OFString from a UTF-8 encoded C * string. * @@ -763,11 +782,11 @@ * @return An initialized OFString */ - (instancetype)initWithFormat: (OFConstantString *)format arguments: (va_list)arguments; -#ifdef OF_HAVE_FILES +# ifdef OF_HAVE_FILES /*! * @brief Initializes an already allocated OFString with the contents of the * specified file in the specified encoding. * * @param path The path to the file @@ -783,11 +802,11 @@ * @param encoding The encoding of the file * @return An initialized OFString */ - (instancetype)initWithContentsOfFile: (OFString *)path encoding: (of_string_encoding_t)encoding; -#endif +# endif /*! * @brief Initializes an already allocated OFString with the contents of the * specified URL. * @@ -1168,11 +1187,11 @@ * @param encoding The encoding to use for the returned OFData * @return The string as OFData with the specified encoding */ - (OFData *)dataWithEncoding: (of_string_encoding_t)encoding; -#ifdef OF_HAVE_FILES +# ifdef OF_HAVE_FILES /*! * @brief Writes the string into the specified file using UTF-8 encoding. * * @param path The path of the file to write to */ @@ -1185,11 +1204,11 @@ * @param path The path of the file to write to * @param encoding The encoding to use to write the string into the file */ - (void)writeToFile: (OFString *)path encoding: (of_string_encoding_t)encoding; -#endif +# endif /*! * @brief Writes the string to the specified URL using UTF-8 encoding. * * @param URL The URL to write to @@ -1203,19 +1222,20 @@ * @param encoding The encoding to use to write the string to the URL */ - (void)writeToURL: (OFURL *)URL encoding: (of_string_encoding_t)encoding; -#ifdef OF_HAVE_BLOCKS +# ifdef OF_HAVE_BLOCKS /*! * Enumerates all lines in the receiver using the specified block. * * @brief block The block to call for each line */ - (void)enumerateLinesUsingBlock: (of_string_line_enumeration_block_t)block; -#endif +# endif @end +#endif #ifdef __cplusplus extern "C" { #endif extern of_string_encoding_t of_string_parse_encoding(OFString *); @@ -1228,24 +1248,26 @@ } #endif OF_ASSUME_NONNULL_END -#import "OFConstantString.h" -#import "OFMutableString.h" -#import "OFString+CryptoHashing.h" -#import "OFString+JSONValue.h" -#ifdef OF_HAVE_FILES -# import "OFString+PathAdditions.h" -#endif -#import "OFString+PropertyListValue.h" -#import "OFString+Serialization.h" -#import "OFString+URLEncoding.h" -#import "OFString+XMLEscaping.h" -#import "OFString+XMLUnescaping.h" - -#if !defined(NSINTEGER_DEFINED) && !__has_feature(modules) +#include "OFConstantString.h" +#include "OFMutableString.h" +#ifdef __OBJC__ +# import "OFString+CryptoHashing.h" +# import "OFString+JSONValue.h" +# ifdef OF_HAVE_FILES +# import "OFString+PathAdditions.h" +# endif +# import "OFString+PropertyListValue.h" +# import "OFString+Serialization.h" +# import "OFString+URLEncoding.h" +# import "OFString+XMLEscaping.h" +# import "OFString+XMLUnescaping.h" +#endif + +#if defined(__OBJC__) && !defined(NSINTEGER_DEFINED) && !__has_feature(modules) /* * Very *ugly* hack required for string boxing literals to work. * * This hack is needed in order to work with `@class NSString` from Apple's * objc/NSString.h - which is included when using modules - as @@ -1256,6 +1278,8 @@ * * TODO: Submit a patch for Clang that makes the boxing classes configurable! */ @interface NSString: OFString @end +#endif + #endif Index: src/OFString.m ================================================================== --- src/OFString.m +++ src/OFString.m @@ -39,10 +39,11 @@ # import "OFFile.h" # import "OFFileManager.h" #endif #import "OFLocale.h" #import "OFStream.h" +#import "OFSystemInfo.h" #import "OFURL.h" #import "OFURLHandler.h" #import "OFUTF8String.h" #import "OFUTF8String+Private.h" #import "OFXMLElement.h" @@ -188,11 +189,11 @@ else if ([string isEqual: @"koi8-r"]) encoding = OF_STRING_ENCODING_KOI8_R; else if ([string isEqual: @"koi8-u"]) encoding = OF_STRING_ENCODING_KOI8_U; else - @throw [OFInvalidEncodingException exception]; + @throw [OFInvalidArgumentException exception]; objc_autoreleasePoolPop(pool); return encoding; } @@ -2703,10 +2704,40 @@ - (OFString *)decomposedStringWithCompatibilityMapping { return decomposedString(self, of_unicode_decomposition_compat_table, OF_UNICODE_DECOMPOSITION_COMPAT_TABLE_SIZE); +} +#endif + +#ifdef OF_WINDOWS +- (OFString *)stringByExpandingWindowsEnvironmentStrings +{ + if ([OFSystemInfo isWindowsNT]) { + wchar_t buffer[512]; + size_t length; + + if ((length = ExpandEnvironmentStringsW(self.UTF16String, + buffer, sizeof(buffer))) == 0) + return self; + + return [OFString stringWithUTF16String: buffer + length: length - 1]; + } else { + of_string_encoding_t encoding = [OFLocale encoding]; + char buffer[512]; + size_t length; + + if ((length = ExpandEnvironmentStringsA( + [self cStringWithEncoding: encoding], buffer, + sizeof(buffer))) == 0) + return self; + + return [OFString stringWithCString: buffer + encoding: encoding + length: length - 1]; + } } #endif #ifdef OF_HAVE_FILES - (void)writeToFile: (OFString *)path Index: src/OFSystemInfo.h ================================================================== --- src/OFSystemInfo.h +++ src/OFSystemInfo.h @@ -56,10 +56,13 @@ @property (class, readonly, nonatomic) bool supportsSHAExtensions; # endif # if defined(OF_POWERPC) || defined(OF_POWERPC64) || defined(DOXYGEN) @property (class, readonly, nonatomic) bool supportsAltiVec; # endif +# ifdef OF_WINDOWS +@property (class, readonly, nonatomic, getter=isWindowsNT) bool windowsNT; +# endif #endif /*! * @brief Returns the size of a page. * @@ -284,11 +287,22 @@ * * @return Whether the CPU and OS support AltiVec */ + (bool)supportsAltiVec; #endif + +#ifdef OF_WINDOWS +/*! + * @brief Returns whether the application is running on Windows NT. + * + * @note This method is only available on Windows. + * + * @return Whether the application is running on Windows NT + */ ++ (bool)isWindowsNT; +#endif + (instancetype)alloc OF_UNAVAILABLE; - (instancetype)init OF_UNAVAILABLE; @end OF_ASSUME_NONNULL_END Index: src/OFSystemInfo.m ================================================================== --- src/OFSystemInfo.m +++ src/OFSystemInfo.m @@ -163,41 +163,42 @@ #elif defined(OF_WINDOWS) # ifdef OF_HAVE_FILES void *pool = objc_autoreleasePoolPush(); @try { - wchar_t systemDir[PATH_MAX]; + of_string_encoding_t encoding = [OFLocale encoding]; + char systemDir[PATH_MAX]; UINT systemDirLen; OFString *systemDirString; - const of_char16_t *path; + const char *path; void *buffer; DWORD bufferLen; - systemDirLen = GetSystemDirectoryW(systemDir, PATH_MAX); + systemDirLen = GetSystemDirectoryA(systemDir, PATH_MAX); if (systemDirLen == 0) return; - systemDirString = [OFString - stringWithUTF16String: systemDir - length: systemDirLen]; - path = [systemDirString stringByAppendingPathComponent: - @"kernel32.dll"].UTF16String; + systemDirString = [OFString stringWithCString: systemDir + encoding: encoding + length: systemDirLen]; + path = [[systemDirString stringByAppendingPathComponent: + @"kernel32.dll"] cStringWithEncoding: encoding]; - if ((bufferLen = GetFileVersionInfoSizeW(path, NULL)) == 0) + if ((bufferLen = GetFileVersionInfoSizeA(path, NULL)) == 0) return; if ((buffer = malloc(bufferLen)) == 0) return; @try { void *data; UINT dataLen; VS_FIXEDFILEINFO *info; - if (!GetFileVersionInfoW(path, 0, bufferLen, buffer)) + if (!GetFileVersionInfoA(path, 0, bufferLen, buffer)) return; - if (!VerQueryValueW(buffer, L"\\", &data, &dataLen) || + if (!VerQueryValueA(buffer, "\\", &data, &dataLen) || dataLen < sizeof(info)) return; info = (VS_FIXEDFILEINFO *)data; @@ -662,11 +663,18 @@ # endif return false; } #endif + +#ifdef OF_WINDOWS ++ (bool)isWindowsNT +{ + return !(GetVersion() & 0x80000000); +} +#endif - (instancetype)init { OF_INVALID_INIT_METHOD } @end DELETED src/OFTCPSocket+Private.h Index: src/OFTCPSocket+Private.h ================================================================== --- src/OFTCPSocket+Private.h +++ src/OFTCPSocket+Private.h @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#import "OFTCPSocket.h" - -OF_ASSUME_NONNULL_BEGIN - -@interface OFTCPSocket () -#ifndef OF_WII -@property (readonly, nonatomic) int of_socketError; -#endif - -- (bool)of_createSocketForAddress: (const of_socket_address_t *)address - errNo: (int *)errNo; -- (bool)of_connectSocketToAddress: (const of_socket_address_t *)address - errNo: (int *)errNo; -- (void)of_closeSocket; -@end - -OF_ASSUME_NONNULL_END Index: src/OFTCPSocket.h ================================================================== --- src/OFTCPSocket.h +++ src/OFTCPSocket.h @@ -29,37 +29,22 @@ #ifdef OF_HAVE_BLOCKS /*! * @brief A block which is called when the socket connected. * - * @param socket The socket which connected * @param exception An exception which occurred while connecting the socket or * `nil` on success */ -typedef void (^of_tcp_socket_async_connect_block_t)(OFTCPSocket *socket, - id _Nullable exception); - -/*! - * @brief A block which is called when the socket accepted a connection. - * - * @param socket The socket which accepted the connection - * @param acceptedSocket The socket which has been accepted - * @param exception An exception which occurred while accepting the socket or - * `nil` on success - * @return A bool whether the same block should be used for the next incoming - * connection - */ -typedef bool (^of_tcp_socket_async_accept_block_t)(OFTCPSocket *socket, - OFTCPSocket *acceptedSocket, id _Nullable exception); +typedef void (^of_tcp_socket_async_connect_block_t)(id _Nullable exception); #endif /*! * @protocol OFTCPSocketDelegate OFTCPSocket.h ObjFW/OFTCPSocket.h * * A delegate for OFTCPSocket. */ -@protocol OFTCPSocketDelegate +@protocol OFTCPSocketDelegate @optional /*! * @brief A method which is called when a socket connected. * * @param socket The socket which connected @@ -70,23 +55,10 @@ */ - (void)socket: (OFTCPSocket *)socket didConnectToHost: (OFString *)host port: (uint16_t)port exception: (nullable id)exception; - -/*! - * @brief A method which is called when a socket accepted a connection. - * - * @param socket The socket which accepted the connection - * @param acceptedSocket The socket which has been accepted - * @param exception An exception that occurred while accepting, or nil on - * success - * @return A bool whether to accept the next incoming connection - */ -- (bool)socket: (OFTCPSocket *)socket - didAcceptSocket: (OFTCPSocket *)acceptedSocket - exception: (nullable id)exception; @end /*! * @class OFTCPSocket OFTCPSocket.h ObjFW/OFTCPSocket.h * @@ -95,12 +67,10 @@ * To connect to a server, create a socket and connect it. * To create a server, create a socket, bind it and listen on it. */ @interface OFTCPSocket: OFStreamSocket { - bool _listening; - of_socket_address_t _remoteAddress; OFString *_Nullable _SOCKS5Host; uint16_t _SOCKS5Port; #ifdef OF_WII uint16_t _port; #endif @@ -110,38 +80,27 @@ #ifdef OF_HAVE_CLASS_PROPERTIES @property (class, nullable, copy, nonatomic) OFString *SOCKS5Host; @property (class, nonatomic) uint16_t SOCKS5Port; #endif -/*! - * @brief Whether the socket is a listening socket. - */ -@property (readonly, nonatomic, getter=isListening) bool listening; - -/*! - * @brief The remote address. - * - * @note This only works for accepted sockets! - */ -@property (readonly, nonatomic) const of_socket_address_t *remoteAddress; - #if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) /*! - * @brief Whether keep alives are enabled for the connection. + * @brief Whether the socket sends keep alives for the connection. * * @warning This is not available on the Wii or Nintendo 3DS! */ -@property (nonatomic, getter=isKeepAliveEnabled) bool keepAliveEnabled; +@property (nonatomic) bool sendsKeepAlives; #endif #ifndef OF_WII /*! - * @brief Whether TCP_NODELAY is enabled for the connection + * @brief Whether sending segments can be delayed. Setting this to NO sets + * TCP_NODELAY on the socket. * * @warning This is not available on the Wii! */ -@property (nonatomic, getter=isTCPNoDelayEnabled) bool TCPNoDelayEnabled; +@property (nonatomic) bool canDelaySendingSegments; #endif /*! * @brief The host to use as a SOCKS5 proxy. */ @@ -154,11 +113,11 @@ /*! * @brief The delegate for asynchronous operations on the socket. * * @note The delegate is retained for as long as asynchronous operations are - * still outstanding. + * still ongoing. */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) id delegate; /*! @@ -254,63 +213,10 @@ * chosen, which can be obtained using the return value. * @return The port the socket was bound to */ - (uint16_t)bindToHost: (OFString *)host port: (uint16_t)port; - -/*! - * @brief Listen on the socket. - * - * @param backlog Maximum length for the queue of pending connections. - */ -- (void)listenWithBacklog: (int)backlog; - -/*! - * @brief Listen on the socket. - */ -- (void)listen; - -/*! - * @brief Accept an incoming connection. - * - * @return An autoreleased OFTCPSocket for the accepted connection. - */ -- (instancetype)accept; - -/*! - * @brief Asynchronously accept an incoming connection. - */ -- (void)asyncAccept; - -/*! - * @brief Asynchronously accept an incoming connection. - * - * @param runLoopMode The run loop mode in which to perform the async accept - */ -- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; - -#ifdef OF_HAVE_BLOCKS -/*! - * @brief Asynchronously accept an incoming connection. - * - * @param block The block to execute when a new connection has been accepted. - * Returns whether the next incoming connection should be accepted - * by the specified block as well. - */ -- (void)asyncAcceptWithBlock: (of_tcp_socket_async_accept_block_t)block; - -/*! - * @brief Asynchronously accept an incoming connection. - * - * @param runLoopMode The run loop mode in which to perform the async accept - * @param block The block to execute when a new connection has been accepted. - * Returns whether the next incoming connection should be accepted - * by the specified block as well. - */ -- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_tcp_socket_async_accept_block_t)block; -#endif @end #ifdef __cplusplus extern "C" { #endif Index: src/OFTCPSocket.m ================================================================== --- src/OFTCPSocket.m +++ src/OFTCPSocket.m @@ -13,16 +13,14 @@ * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ -#define OF_TCP_SOCKET_M #define __NO_EXT_QNX #include "config.h" -#include #include #include #include #include @@ -29,32 +27,25 @@ #ifdef HAVE_FCNTL_H # include #endif #import "OFTCPSocket.h" -#import "OFTCPSocket+Private.h" -#import "OFDate.h" #import "OFDNSResolver.h" #import "OFData.h" +#import "OFDate.h" +#import "OFIPSocketAsyncConnector.h" #import "OFRunLoop.h" #import "OFRunLoop+Private.h" #import "OFString.h" +#import "OFTCPSocketSOCKS5Connector.h" #import "OFThread.h" -#import "OFTimer.h" -#import "OFAcceptFailedException.h" #import "OFAlreadyConnectedException.h" #import "OFBindFailedException.h" -#import "OFConnectionFailedException.h" #import "OFGetOptionFailedException.h" -#import "OFInvalidArgumentException.h" -#import "OFInvalidFormatException.h" -#import "OFListenFailedException.h" #import "OFNotImplementedException.h" #import "OFNotOpenException.h" -#import "OFOutOfMemoryException.h" -#import "OFOutOfRangeException.h" #import "OFSetOptionFailedException.h" #import "socket.h" #import "socket_helpers.h" @@ -64,544 +55,19 @@ Class of_tls_socket_class = Nil; static OFString *defaultSOCKS5Host = nil; static uint16_t defaultSOCKS5Port = 1080; -@interface OFTCPSocketAsyncConnectDelegate: OFObject -{ - OFTCPSocket *_socket; - OFString *_host; - uint16_t _port; - OFString *_SOCKS5Host; - uint16_t _SOCKS5Port; - id _delegate; -#ifdef OF_HAVE_BLOCKS - of_tcp_socket_async_connect_block_t _block; -#endif - id _exception; - OFData *_socketAddresses; - size_t _socketAddressesIndex; - enum { - SOCKS5_STATE_SEND_AUTHENTICATION = 1, - SOCKS5_STATE_READ_VERSION, - SOCKS5_STATE_SEND_REQUEST, - SOCKS5_STATE_READ_RESPONSE, - SOCKS5_STATE_READ_ADDRESS, - SOCKS5_STATE_READ_ADDRESS_LENGTH, - } _SOCKS5State; - /* Longest read is domain name (max 255 bytes) + port */ - unsigned char _buffer[257]; - OFMutableData *_request; -} - -- (instancetype)initWithSocket: (OFTCPSocket *)sock - host: (OFString *)host - port: (uint16_t)port - SOCKS5Host: (OFString *)SOCKS5Host - SOCKS5Port: (uint16_t)SOCKS5Port - delegate: (id )delegate; -#ifdef OF_HAVE_BLOCKS -- (instancetype)initWithSocket: (OFTCPSocket *)sock - host: (OFString *)host - port: (uint16_t)port - SOCKS5Host: (OFString *)SOCKS5Host - SOCKS5Port: (uint16_t)SOCKS5Port - block: (of_tcp_socket_async_connect_block_t)block; -#endif -- (void)didConnect; -- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; -- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode; -- (void)sendSOCKS5Request; +@interface OFTCPSocket () @end @interface OFTCPSocketConnectDelegate: OFObject { @public bool _done; id _exception; } -@end - -@implementation OFTCPSocketAsyncConnectDelegate -- (instancetype)initWithSocket: (OFTCPSocket *)sock - host: (OFString *)host - port: (uint16_t)port - SOCKS5Host: (OFString *)SOCKS5Host - SOCKS5Port: (uint16_t)SOCKS5Port - delegate: (id )delegate -{ - self = [super init]; - - @try { - _socket = [sock retain]; - _host = [host copy]; - _port = port; - _SOCKS5Host = [SOCKS5Host copy]; - _SOCKS5Port = SOCKS5Port; - _delegate = [delegate retain]; - - _socket.delegate = self; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -#ifdef OF_HAVE_BLOCKS -- (instancetype)initWithSocket: (OFTCPSocket *)sock - host: (OFString *)host - port: (uint16_t)port - SOCKS5Host: (OFString *)SOCKS5Host - SOCKS5Port: (uint16_t)SOCKS5Port - block: (of_tcp_socket_async_connect_block_t)block -{ - self = [super init]; - - @try { - _socket = [sock retain]; - _host = [host copy]; - _port = port; - _SOCKS5Host = [SOCKS5Host copy]; - _SOCKS5Port = SOCKS5Port; - _block = [block copy]; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} -#endif - -- (void)dealloc -{ -#ifdef OF_HAVE_BLOCKS - if (_block == NULL) -#endif - if (_socket.delegate == self) - _socket.delegate = _delegate; - - [_socket release]; - [_host release]; - [_SOCKS5Host release]; - [_delegate release]; -#ifdef OF_HAVE_BLOCKS - [_block release]; -#endif - [_exception release]; - [_socketAddresses release]; - [_request release]; - - [super dealloc]; -} - -- (void)didConnect -{ - if (_exception == nil) - _socket.blocking = true; - -#ifdef OF_HAVE_BLOCKS - if (_block != NULL) - _block(_socket, _exception); - else { -#endif - _socket.delegate = _delegate; - - if ([_delegate respondsToSelector: - @selector(socket:didConnectToHost:port:exception:)]) - [_delegate socket: _socket - didConnectToHost: _host - port: _port - exception: _exception]; -#ifdef OF_HAVE_BLOCKS - } -#endif -} - -- (void)of_socketDidConnect: (OFTCPSocket *)sock - exception: (id)exception -{ - if (exception != nil) { - /* - * self might be retained only by the pending async requests, - * which we're about to cancel. - */ - [[self retain] autorelease]; - - [sock cancelAsyncRequests]; - [sock of_closeSocket]; - - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [exception retain]; - [self didConnect]; - } else { - /* - * We must not call it before returning, as otherwise - * the new socket would be removed from the queue upon - * return. - */ - OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; - SEL selector = - @selector(tryNextAddressWithRunLoopMode:); - OFTimer *timer = [OFTimer - timerWithTimeInterval: 0 - target: self - selector: selector - object: runLoop.currentMode - repeats: false]; - [runLoop addTimer: timer - forMode: runLoop.currentMode]; - } - - return; - } - - if (_SOCKS5Host != nil) - [self sendSOCKS5Request]; - else - [self didConnect]; -} - -- (void)tryNextAddressWithRunLoopMode: (of_run_loop_mode_t)runLoopMode -{ - of_socket_address_t address = *(const of_socket_address_t *) - [_socketAddresses itemAtIndex: _socketAddressesIndex++]; - int errNo; - - if (_SOCKS5Host != nil) - of_socket_address_set_port(&address, _SOCKS5Port); - else - of_socket_address_set_port(&address, _port); - - if (![_socket of_createSocketForAddress: &address - errNo: &errNo]) { - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return; - } - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; - } - -#if defined(OF_NINTENDO_3DS) || defined(OF_WII) - /* - * On Wii and 3DS, connect() fails if non-blocking is enabled. - * - * Additionally, on Wii, there is no getsockopt(), so it would not be - * possible to get the error (or success) after connecting anyway. - * - * So for now, connecting is blocking on Wii and 3DS. - * - * FIXME: Use a different thread as a work around. - */ - _socket.blocking = true; -#else - _socket.blocking = false; -#endif - - if (![_socket of_connectSocketToAddress: &address - errNo: &errNo]) { -#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) - if (errNo == EINPROGRESS) { - [OFRunLoop of_addAsyncConnectForTCPSocket: _socket - mode: runLoopMode - delegate: self]; - return; - } else { -#endif - [_socket of_closeSocket]; - - if (_socketAddressesIndex >= _socketAddresses.count) { - _exception = [[OFConnectionFailedException - alloc] initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return; - } - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; -#if !defined(OF_NINTENDO_3DS) && !defined(OF_WII) - } -#endif - } - -#if defined(OF_NINTENDO_3DS) || defined(OF_WII) - _socket.blocking = false; -#endif - - [self didConnect]; -} - -- (void)resolver: (OFDNSResolver *)resolver - didResolveHost: (OFString *)host - addresses: (OFData *)addresses - exception: (id)exception -{ - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return; - } - - _socketAddresses = [addresses copy]; - - [self tryNextAddressWithRunLoopMode: - [OFRunLoop currentRunLoop].currentMode]; -} - -- (void)startWithRunLoopMode: (of_run_loop_mode_t)runLoopMode -{ - OFString *host; - uint16_t port; - - if (_SOCKS5Host != nil) { - if (_host.UTF8StringLength > 255) - @throw [OFOutOfRangeException exception]; - - host = _SOCKS5Host; - port = _SOCKS5Port; - } else { - host = _host; - port = _port; - } - - @try { - of_socket_address_t address = - of_socket_address_parse_ip(host, port); - - _socketAddresses = [[OFData alloc] - initWithItems: &address - itemSize: sizeof(address) - count: 1]; - - [self tryNextAddressWithRunLoopMode: runLoopMode]; - return; - } @catch (OFInvalidFormatException *e) { - } - - [[OFThread DNSResolver] - asyncResolveAddressesForHost: host - addressFamily: OF_SOCKET_ADDRESS_FAMILY_ANY - runLoopMode: runLoopMode - delegate: self]; -} - -- (void)sendSOCKS5Request -{ - OFData *data = [OFData dataWithItems: "\x05\x01\x00" - count: 3]; - - _SOCKS5State = SOCKS5_STATE_SEND_AUTHENTICATION; - [_socket asyncWriteData: data - runLoopMode: [OFRunLoop currentRunLoop].currentMode]; -} - -- (bool)stream: (OFStream *)sock - didReadIntoBuffer: (void *)buffer - length: (size_t)length - exception: (id)exception -{ - of_run_loop_mode_t runLoopMode; - unsigned char *SOCKSVersion; - uint8_t hostLength; - unsigned char port[2]; - unsigned char *response, *addressLength; - - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return false; - } - - runLoopMode = [OFRunLoop currentRunLoop].currentMode; - - switch (_SOCKS5State) { - case SOCKS5_STATE_READ_VERSION: - SOCKSVersion = buffer; - - if (SOCKSVersion[0] != 5 || SOCKSVersion[1] != 0) { - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - [_request release]; - _request = [[OFMutableData alloc] init]; - - [_request addItems: "\x05\x01\x00\x03" - count: 4]; - - hostLength = (uint8_t)_host.UTF8StringLength; - [_request addItem: &hostLength]; - [_request addItems: _host.UTF8String - count: hostLength]; - - port[0] = _port >> 8; - port[1] = _port & 0xFF; - [_request addItems: port - count: 2]; - - _SOCKS5State = SOCKS5_STATE_SEND_REQUEST; - [_socket asyncWriteData: _request - runLoopMode: runLoopMode]; - return false; - case SOCKS5_STATE_READ_RESPONSE: - response = buffer; - - if (response[0] != 5 || response[2] != 0) { - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - if (response[1] != 0) { - int errNo; - - switch (response[1]) { - case 0x02: - errNo = EPERM; - break; - case 0x03: - errNo = ENETUNREACH; - break; - case 0x04: - errNo = EHOSTUNREACH; - break; - case 0x05: - errNo = ECONNREFUSED; - break; - case 0x06: - errNo = ETIMEDOUT; - break; - case 0x07: - errNo = EOPNOTSUPP; - break; - case 0x08: - errNo = EAFNOSUPPORT; - break; - default: -#ifdef EPROTO - errNo = EPROTO; -#else - errNo = 0; -#endif - break; - } - - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: _socket - errNo: errNo]; - [self didConnect]; - return false; - } - - /* Skip the rest of the response */ - switch (response[3]) { - case 1: /* IPv4 */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 4 + 2 - runLoopMode: runLoopMode]; - return false; - case 3: /* Domain name */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS_LENGTH; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 1 - runLoopMode: runLoopMode]; - return false; - case 4: /* IPv6 */ - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 16 + 2 - runLoopMode: runLoopMode]; - return false; - default: - _exception = [[OFConnectionFailedException alloc] - initWithHost: _host - port: _port - socket: self - errNo: EPROTONOSUPPORT]; - [self didConnect]; - return false; - } - - return false; - case SOCKS5_STATE_READ_ADDRESS: - [self didConnect]; - return false; - case SOCKS5_STATE_READ_ADDRESS_LENGTH: - addressLength = buffer; - - _SOCKS5State = SOCKS5_STATE_READ_ADDRESS; - [_socket asyncReadIntoBuffer: _buffer - exactLength: addressLength[0] + 2 - runLoopMode: runLoopMode]; - return false; - default: - assert(0); - return false; - } -} - -- (OFData *)stream: (OFStream *)sock - didWriteData: (OFData *)data - bytesWritten: (size_t)bytesWritten - exception: (id)exception -{ - of_run_loop_mode_t runLoopMode; - - if (exception != nil) { - _exception = [exception retain]; - [self didConnect]; - return nil; - } - - runLoopMode = [OFRunLoop currentRunLoop].currentMode; - - switch (_SOCKS5State) { - case SOCKS5_STATE_SEND_AUTHENTICATION: - _SOCKS5State = SOCKS5_STATE_READ_VERSION; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 2 - runLoopMode: runLoopMode]; - return nil; - case SOCKS5_STATE_SEND_REQUEST: - [_request release]; - _request = nil; - - _SOCKS5State = SOCKS5_STATE_READ_RESPONSE; - [_socket asyncReadIntoBuffer: _buffer - exactLength: 4 - runLoopMode: runLoopMode]; - return nil; - default: - assert(0); - return nil; - } -} @end @implementation OFTCPSocketConnectDelegate - (void)dealloc { @@ -649,11 +115,10 @@ - (instancetype)init { self = [super init]; @try { - _socket = INVALID_SOCKET; _SOCKS5Host = [defaultSOCKS5Host copy]; _SOCKS5Port = defaultSOCKS5Port; } @catch (id e) { [self release]; @throw e; @@ -697,10 +162,11 @@ errNo: (int *)errNo { if (_socket == INVALID_SOCKET) @throw [OFNotOpenException exceptionWithObject: self]; + /* Cast needed for AmigaOS, where the argument is declared non-const */ if (connect(_socket, (struct sockaddr *)&address->sockaddr.sockaddr, address->length) != 0) { *errNo = of_socket_errno(); return false; } @@ -712,29 +178,15 @@ { closesocket(_socket); _socket = INVALID_SOCKET; } -#ifndef OF_WII -- (int)of_socketError -{ - int errNo; - socklen_t len = sizeof(errNo); - - if (getsockopt(_socket, SOL_SOCKET, SO_ERROR, (char *)&errNo, - &len) != 0) - return of_socket_errno(); - - return errNo; -} -#endif - - (void)connectToHost: (OFString *)host port: (uint16_t)port { void *pool = objc_autoreleasePoolPush(); - id delegate = [_delegate retain]; + id delegate = _delegate; OFTCPSocketConnectDelegate *connectDelegate = [[[OFTCPSocketConnectDelegate alloc] init] autorelease]; OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; self.delegate = connectDelegate; @@ -769,19 +221,37 @@ - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port runLoopMode: (of_run_loop_mode_t)runLoopMode { void *pool = objc_autoreleasePoolPush(); + id delegate; + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + if (_SOCKS5Host != nil) { + delegate = [[[OFTCPSocketSOCKS5Connector alloc] + initWithSocket: self + host: host + port: port + delegate: _delegate +#ifdef OF_HAVE_BLOCKS + block: NULL +#endif + ] autorelease]; + host = _SOCKS5Host; + port = _SOCKS5Port; + } else + delegate = _delegate; - [[[[OFTCPSocketAsyncConnectDelegate alloc] + [[[[OFIPSocketAsyncConnector alloc] initWithSocket: self host: host port: port - SOCKS5Host: _SOCKS5Host - SOCKS5Port: _SOCKS5Port - delegate: _delegate] autorelease] - startWithRunLoopMode: runLoopMode]; + delegate: delegate + block: NULL + ] autorelease] startWithRunLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } #ifdef OF_HAVE_BLOCKS @@ -799,18 +269,32 @@ port: (uint16_t)port runLoopMode: (of_run_loop_mode_t)runLoopMode block: (of_tcp_socket_async_connect_block_t)block { void *pool = objc_autoreleasePoolPush(); + id delegate = nil; + + if (_socket != INVALID_SOCKET) + @throw [OFAlreadyConnectedException exceptionWithSocket: self]; + + if (_SOCKS5Host != nil) { + delegate = [[[OFTCPSocketSOCKS5Connector alloc] + initWithSocket: self + host: host + port: port + delegate: nil + block: block] autorelease]; + host = _SOCKS5Host; + port = _SOCKS5Port; + } - [[[[OFTCPSocketAsyncConnectDelegate alloc] + [[[[OFIPSocketAsyncConnector alloc] initWithSocket: self host: host port: port - SOCKS5Host: _SOCKS5Host - SOCKS5Port: _SOCKS5Port - block: block] autorelease] + delegate: delegate + block: (delegate == nil ? block : NULL)] autorelease] startWithRunLoopMode: runLoopMode]; objc_autoreleasePoolPop(pool); } #endif @@ -846,11 +330,11 @@ exceptionWithHost: host port: port socket: self errNo: of_socket_errno()]; - _blocking = true; + _canBlock = true; #if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); #endif @@ -951,204 +435,68 @@ socket: self errNo: EADDRNOTAVAIL]; #endif } -- (void)listen -{ - [self listenWithBacklog: SOMAXCONN]; -} - -- (void)listenWithBacklog: (int)backlog -{ - if (_socket == INVALID_SOCKET) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (listen(_socket, backlog) == -1) - @throw [OFListenFailedException - exceptionWithSocket: self - backlog: backlog - errNo: of_socket_errno()]; - - _listening = true; -} - -- (instancetype)accept -{ - OFTCPSocket *client = [[[[self class] alloc] init] autorelease]; -#if (!defined(HAVE_PACCEPT) && !defined(HAVE_ACCEPT4)) || !defined(SOCK_CLOEXEC) -# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) - int flags; -# endif -#endif - - client->_remoteAddress.length = - (socklen_t)sizeof(client->_remoteAddress.sockaddr); - -#if defined(HAVE_PACCEPT) && defined(SOCK_CLOEXEC) - if ((client->_socket = paccept(_socket, - &client->_remoteAddress.sockaddr.sockaddr, - &client->_remoteAddress.length, NULL, SOCK_CLOEXEC)) == - INVALID_SOCKET) - @throw [OFAcceptFailedException - exceptionWithSocket: self - errNo: of_socket_errno()]; -#elif defined(HAVE_ACCEPT4) && defined(SOCK_CLOEXEC) - if ((client->_socket = accept4(_socket, - &client->_remoteAddress.sockaddr.sockaddr, - &client->_remoteAddress.length, SOCK_CLOEXEC)) == INVALID_SOCKET) - @throw [OFAcceptFailedException - exceptionWithSocket: self - errNo: of_socket_errno()]; -#else - if ((client->_socket = accept(_socket, - &client->_remoteAddress.sockaddr.sockaddr, - &client->_remoteAddress.length)) == INVALID_SOCKET) - @throw [OFAcceptFailedException - exceptionWithSocket: self - errNo: of_socket_errno()]; - -# if defined(HAVE_FCNTL) && defined(FD_CLOEXEC) - if ((flags = fcntl(client->_socket, F_GETFD, 0)) != -1) - fcntl(client->_socket, F_SETFD, flags | FD_CLOEXEC); -# endif -#endif - - assert(client->_remoteAddress.length <= - (socklen_t)sizeof(client->_remoteAddress.sockaddr)); - - switch (client->_remoteAddress.sockaddr.sockaddr.sa_family) { - case AF_INET: - client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV4; - break; -#ifdef OF_HAVE_IPV6 - case AF_INET6: - client->_remoteAddress.family = OF_SOCKET_ADDRESS_FAMILY_IPV6; - break; -#endif - default: - client->_remoteAddress.family = - OF_SOCKET_ADDRESS_FAMILY_UNKNOWN; - break; - } - - return client; -} - -- (void)asyncAccept -{ - [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default]; -} - -- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode -{ - [OFRunLoop of_addAsyncAcceptForTCPSocket: self - mode: runLoopMode -# ifdef OF_HAVE_BLOCKS - block: NULL -# endif - delegate: _delegate]; -} - -#ifdef OF_HAVE_BLOCKS -- (void)asyncAcceptWithBlock: (of_tcp_socket_async_accept_block_t)block -{ - [self asyncAcceptWithRunLoopMode: of_run_loop_mode_default - block: block]; -} - -- (void)asyncAcceptWithRunLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_tcp_socket_async_accept_block_t)block -{ - [OFRunLoop of_addAsyncAcceptForTCPSocket: self - mode: runLoopMode - block: block - delegate: nil]; -} -#endif - -- (const of_socket_address_t *)remoteAddress -{ - if (_socket == INVALID_SOCKET) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (_remoteAddress.length == 0) - @throw [OFInvalidArgumentException exception]; - - if (_remoteAddress.length > (socklen_t)sizeof(_remoteAddress.sockaddr)) - @throw [OFOutOfRangeException exception]; - - return &_remoteAddress; -} - -- (bool)isListening -{ - return _listening; -} - -#if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) -- (void)setKeepAliveEnabled: (bool)enabled -{ - int v = enabled; +#if !defined(OF_WII) && !defined(OF_NINTENDO_3DS) +- (void)setSendsKeepAlives: (bool)sendsKeepAlives +{ + int v = sendsKeepAlives; if (setsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&v, (socklen_t)sizeof(v)) != 0) @throw [OFSetOptionFailedException exceptionWithObject: self errNo: of_socket_errno()]; } -- (bool)isKeepAliveEnabled +- (bool)sendsKeepAlives { int v; socklen_t len = sizeof(v); if (getsockopt(_socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&v, &len) != 0 || len != sizeof(v)) @throw [OFGetOptionFailedException - exceptionWithStream: self + exceptionWithObject: self errNo: of_socket_errno()]; return v; } #endif #ifndef OF_WII -- (void)setTCPNoDelayEnabled: (bool)enabled +- (void)setCanDelaySendingSegments: (bool)canDelaySendingSegments { - int v = enabled; + int v = !canDelaySendingSegments; if (setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&v, (socklen_t)sizeof(v)) != 0) @throw [OFSetOptionFailedException exceptionWithObject: self errNo: of_socket_errno()]; } -- (bool)isTCPNoDelayEnabled +- (bool)canDelaySendingSegments { int v; socklen_t len = sizeof(v); if (getsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, (char *)&v, &len) != 0 || len != sizeof(v)) @throw [OFGetOptionFailedException - exceptionWithStream: self + exceptionWithObject: self errNo: of_socket_errno()]; - return v; + return !v; } #endif - (void)close { - _listening = false; - - memset(&_remoteAddress, 0, sizeof(_remoteAddress)); - #ifdef OF_WII _port = 0; #endif [super close]; } @end ADDED src/OFTCPSocketSOCKS5Connector.h Index: src/OFTCPSocketSOCKS5Connector.h ================================================================== --- src/OFTCPSocketSOCKS5Connector.h +++ src/OFTCPSocketSOCKS5Connector.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFTCPSocket.h" + +OF_ASSUME_NONNULL_BEGIN + +@class OFString; + +@interface OFTCPSocketSOCKS5Connector: OFObject +{ + OFTCPSocket *_socket; + OFString *_host; + uint16_t _port; + id _Nullable _delegate; +#ifdef OF_HAVE_BLOCKS + of_tcp_socket_async_connect_block_t _Nullable _block; +#endif + id _Nullable _exception; + enum { + OF_SOCKS5_STATE_SEND_AUTHENTICATION = 1, + OF_SOCKS5_STATE_READ_VERSION, + OF_SOCKS5_STATE_SEND_REQUEST, + OF_SOCKS5_STATE_READ_RESPONSE, + OF_SOCKS5_STATE_READ_ADDRESS, + OF_SOCKS5_STATE_READ_ADDRESS_LENGTH, + } _SOCKS5State; + /* Longest read is domain name (max 255 bytes) + port */ + unsigned char _buffer[257]; + OFMutableData *_Nullable _request; +} + +- (instancetype)initWithSocket: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (nullable id )delegate +#ifdef OF_HAVE_BLOCKS + block: (nullable of_tcp_socket_async_connect_block_t) + block +#endif +; +- (void)didConnect; +@end + +OF_ASSUME_NONNULL_END ADDED src/OFTCPSocketSOCKS5Connector.m Index: src/OFTCPSocketSOCKS5Connector.m ================================================================== --- src/OFTCPSocketSOCKS5Connector.m +++ src/OFTCPSocketSOCKS5Connector.m @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#import "OFTCPSocketSOCKS5Connector.h" +#import "OFData.h" +#import "OFRunLoop.h" +#import "OFString.h" + +#import "OFConnectionFailedException.h" + +@implementation OFTCPSocketSOCKS5Connector +- (instancetype)initWithSocket: (OFTCPSocket *)sock + host: (OFString *)host + port: (uint16_t)port + delegate: (id )delegate +#ifdef OF_HAVE_BLOCKS + block: (of_tcp_socket_async_connect_block_t)block +#endif +{ + self = [super init]; + + @try { + _socket = [sock retain]; + _host = [host copy]; + _port = port; + _delegate = [delegate retain]; +#ifdef OF_HAVE_BLOCKS + _block = [block copy]; +#endif + + _socket.delegate = self; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_socket.delegate == self) + _socket.delegate = _delegate; + + [_socket release]; + [_host release]; + [_delegate release]; +#ifdef OF_HAVE_BLOCKS + [_block release]; +#endif + [_exception release]; + [_request release]; + + [super dealloc]; +} + +- (void)didConnect +{ + _socket.delegate = _delegate; + +#ifdef OF_HAVE_BLOCKS + if (_block != NULL) + _block(_exception); + else { +#endif + if ([_delegate respondsToSelector: + @selector(socket:didConnectToHost:port:exception:)]) + [_delegate socket: _socket + didConnectToHost: _host + port: _port + exception: _exception]; +#ifdef OF_HAVE_BLOCKS + } +#endif +} + +- (void)socket: (OFTCPSocket *)sock + didConnectToHost: (OFString *)host + port: (uint16_t)port + exception: (id)exception +{ + OFData *data; + + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return; + } + + data = [OFData dataWithItems: "\x05\x01\x00" + count: 3]; + + _SOCKS5State = OF_SOCKS5_STATE_SEND_AUTHENTICATION; + [_socket asyncWriteData: data + runLoopMode: [OFRunLoop currentRunLoop].currentMode]; +} + +- (bool)stream: (OFStream *)sock + didReadIntoBuffer: (void *)buffer + length: (size_t)length + exception: (id)exception +{ + of_run_loop_mode_t runLoopMode; + unsigned char *SOCKSVersion; + uint8_t hostLength; + unsigned char port[2]; + unsigned char *response, *addressLength; + + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return false; + } + + runLoopMode = [OFRunLoop currentRunLoop].currentMode; + + switch (_SOCKS5State) { + case OF_SOCKS5_STATE_READ_VERSION: + SOCKSVersion = buffer; + + if (SOCKSVersion[0] != 5 || SOCKSVersion[1] != 0) { + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + [_request release]; + _request = [[OFMutableData alloc] init]; + + [_request addItems: "\x05\x01\x00\x03" + count: 4]; + + hostLength = (uint8_t)_host.UTF8StringLength; + [_request addItem: &hostLength]; + [_request addItems: _host.UTF8String + count: hostLength]; + + port[0] = _port >> 8; + port[1] = _port & 0xFF; + [_request addItems: port + count: 2]; + + _SOCKS5State = OF_SOCKS5_STATE_SEND_REQUEST; + [_socket asyncWriteData: _request + runLoopMode: runLoopMode]; + return false; + case OF_SOCKS5_STATE_READ_RESPONSE: + response = buffer; + + if (response[0] != 5 || response[2] != 0) { + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + if (response[1] != 0) { + int errNo; + + switch (response[1]) { + case 0x02: + errNo = EPERM; + break; + case 0x03: + errNo = ENETUNREACH; + break; + case 0x04: + errNo = EHOSTUNREACH; + break; + case 0x05: + errNo = ECONNREFUSED; + break; + case 0x06: + errNo = ETIMEDOUT; + break; + case 0x07: + errNo = EOPNOTSUPP; + break; + case 0x08: + errNo = EAFNOSUPPORT; + break; + default: +#ifdef EPROTO + errNo = EPROTO; +#else + errNo = 0; +#endif + break; + } + + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: _socket + errNo: errNo]; + [self didConnect]; + return false; + } + + /* Skip the rest of the response */ + switch (response[3]) { + case 1: /* IPv4 */ + _SOCKS5State = OF_SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 4 + 2 + runLoopMode: runLoopMode]; + return false; + case 3: /* Domain name */ + _SOCKS5State = OF_SOCKS5_STATE_READ_ADDRESS_LENGTH; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 1 + runLoopMode: runLoopMode]; + return false; + case 4: /* IPv6 */ + _SOCKS5State = OF_SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 16 + 2 + runLoopMode: runLoopMode]; + return false; + default: + _exception = [[OFConnectionFailedException alloc] + initWithHost: _host + port: _port + socket: self + errNo: EPROTONOSUPPORT]; + [self didConnect]; + return false; + } + + return false; + case OF_SOCKS5_STATE_READ_ADDRESS: + [self didConnect]; + return false; + case OF_SOCKS5_STATE_READ_ADDRESS_LENGTH: + addressLength = buffer; + + _SOCKS5State = OF_SOCKS5_STATE_READ_ADDRESS; + [_socket asyncReadIntoBuffer: _buffer + exactLength: addressLength[0] + 2 + runLoopMode: runLoopMode]; + return false; + default: + assert(0); + return false; + } +} + +- (OFData *)stream: (OFStream *)sock + didWriteData: (OFData *)data + bytesWritten: (size_t)bytesWritten + exception: (id)exception +{ + of_run_loop_mode_t runLoopMode; + + if (exception != nil) { + _exception = [exception retain]; + [self didConnect]; + return nil; + } + + runLoopMode = [OFRunLoop currentRunLoop].currentMode; + + switch (_SOCKS5State) { + case OF_SOCKS5_STATE_SEND_AUTHENTICATION: + _SOCKS5State = OF_SOCKS5_STATE_READ_VERSION; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 2 + runLoopMode: runLoopMode]; + return nil; + case OF_SOCKS5_STATE_SEND_REQUEST: + [_request release]; + _request = nil; + + _SOCKS5State = OF_SOCKS5_STATE_READ_RESPONSE; + [_socket asyncReadIntoBuffer: _buffer + exactLength: 4 + runLoopMode: runLoopMode]; + return nil; + default: + assert(0); + return nil; + } +} +@end Index: src/OFTLSSocket.h ================================================================== --- src/OFTLSSocket.h +++ src/OFTLSSocket.h @@ -79,16 +79,15 @@ */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) const char *privateKeyPassphrase; /*! - * @brief Whether certificate verification is enabled. + * @brief Whether certificates are verified. * * The default is enabled. */ -@property (nonatomic, getter=isCertificateVerificationEnabled) - bool certificateVerificationEnabled; +@property (nonatomic) bool verifiesCertificates; /*! * @brief Initializes the TLS socket with the specified TCP socket as its * underlying socket. * Index: src/OFTarArchive.m ================================================================== --- src/OFTarArchive.m +++ src/OFTarArchive.m @@ -98,26 +98,23 @@ _mode = OF_TAR_ARCHIVE_MODE_APPEND; else @throw [OFInvalidArgumentException exception]; if (_mode == OF_TAR_ARCHIVE_MODE_APPEND) { - union { - char c[1024]; - uint32_t u32[1024 / sizeof(uint32_t)]; - } buffer; + uint32_t buffer[1024 / sizeof(uint32_t)]; bool empty = true; if (![_stream isKindOfClass: [OFSeekableStream class]]) @throw [OFInvalidArgumentException exception]; [(OFSeekableStream *)_stream seekToOffset: -1024 whence: SEEK_END]; - [_stream readIntoBuffer: buffer.c + [_stream readIntoBuffer: buffer exactLength: 1024]; for (size_t i = 0; i < 1024 / sizeof(uint32_t); i++) - if (buffer.u32[i] != 0) + if (buffer[i] != 0) empty = false; if (!empty) @throw [OFInvalidFormatException exception]; @@ -166,14 +163,11 @@ } - (OFTarArchiveEntry *)nextEntry { OFTarArchiveEntry *entry; - union { - unsigned char c[512]; - uint32_t u32[512 / sizeof(uint32_t)]; - } buffer; + uint32_t buffer[512 / sizeof(uint32_t)]; bool empty = true; if (_mode != OF_TAR_ARCHIVE_MODE_READ) @throw [OFInvalidArgumentException exception]; @@ -187,30 +181,30 @@ _lastReturnedStream = nil; if (_stream.atEndOfStream) return nil; - [_stream readIntoBuffer: buffer.c + [_stream readIntoBuffer: buffer exactLength: 512]; for (size_t i = 0; i < 512 / sizeof(uint32_t); i++) - if (buffer.u32[i] != 0) + if (buffer[i] != 0) empty = false; if (empty) { - [_stream readIntoBuffer: buffer.c + [_stream readIntoBuffer: buffer exactLength: 512]; for (size_t i = 0; i < 512 / sizeof(uint32_t); i++) - if (buffer.u32[i] != 0) + if (buffer[i] != 0) @throw [OFInvalidFormatException exception]; return nil; } entry = [[[OFTarArchiveEntry alloc] - of_initWithHeader: buffer.c + of_initWithHeader: (unsigned char *)buffer encoding: _encoding] autorelease]; _lastReturnedStream = [[OFTarArchiveFileReadStream alloc] of_initWithStream: _stream entry: entry]; @@ -502,22 +496,22 @@ @throw [OFTruncatedDataException exception]; remainder = 512 - _entry.size % 512; if (remainder != 512) { - bool wasWriteBuffered = _stream.writeBuffered; + bool didBufferWrites = _stream.buffersWrites; - [_stream setWriteBuffered: true]; + _stream.buffersWrites = true; while (remainder--) [_stream writeInt8: 0]; [_stream flushWriteBuffer]; - _stream.writeBuffered = wasWriteBuffered; + _stream.buffersWrites = didBufferWrites; } [_stream release]; _stream = nil; [super close]; } @end Index: src/OFUDPSocket.h ================================================================== --- src/OFUDPSocket.h +++ src/OFUDPSocket.h @@ -13,90 +13,22 @@ * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ -#import "OFObject.h" -#import "OFKernelEventObserver.h" -#import "OFRunLoop.h" - -#import "socket.h" +#import "OFDatagramSocket.h" OF_ASSUME_NONNULL_BEGIN -/*! @file */ - -@class OFUDPSocket; - -#ifdef OF_HAVE_BLOCKS -/*! - * @brief A block which is called when a packet has been received. - * - * @param socket The UDP socket which received a packet - * @param buffer The buffer the packet has been written to - * @param length The length of the packet - * @param sender The address of the sender of the packet - * @param exception An exception which occurred while receiving or `nil` on - * success - * @return A bool whether the same block should be used for the next receive - */ -typedef bool (^of_udp_socket_async_receive_block_t)( - OFUDPSocket *_Nonnull socket, void *_Nonnull buffer, size_t length, - const of_socket_address_t *_Nonnull sender, id _Nullable exception); - -/*! - * @brief A block which is called when a packet has been sent. - * - * @param socket The UDP socket which sent a packet - * @param data The data which was sent - * @param receiver The receiver for the UDP packet - * @param exception An exception which occurred while reading or `nil` on - * success - * @return The data to repeat the send with or nil if it should not repeat - */ -typedef OFData *_Nullable (^of_udp_socket_async_send_data_block_t)( - OFUDPSocket *_Nonnull socket, OFData *_Nonnull data, - const of_socket_address_t *_Nonnull receiver, id _Nullable exception); -#endif +@class OFString; /*! * @protocol OFUDPSocketDelegate OFUDPSocket.h ObjFW/OFUDPSocket.h * * @brief A delegate for OFUDPSocket. */ -@protocol OFUDPSocketDelegate -@optional -/*! - * @brief This method is called when a packet has been received. - * - * @param socket The UDP socket which received a packet - * @param buffer The buffer the packet has been written to - * @param length The length of the packet - * @param sender The address of the sender of the packet - * @param exception An exception that occurred while receiving, or nil on - * success - * @return A bool whether the same block should be used for the next receive - */ -- (bool)socket: (OFUDPSocket *)socket - didReceiveIntoBuffer: (void *)buffer - length: (size_t)length - sender: (const of_socket_address_t *_Nonnull)sender - exception: (nullable id)exception; - -/*! - * @brief This which is called when a packet has been sent. - * - * @param socket The UDP socket which sent a packet - * @param data The data which was sent - * @param receiver The receiver for the UDP packet - * @param exception An exception that occurred while sending, or nil on success - * @return The data to repeat the send with or nil if it should not repeat - */ -- (nullable OFData *)socket: (OFUDPSocket *)socket - didSendData: (OFData *)data - receiver: (const of_socket_address_t *_Nonnull)receiver - exception: (nullable id)exception; +@protocol OFUDPSocketDelegate @end /*! * @class OFUDPSocket OFUDPSocket.h ObjFW/OFUDPSocket.h * @@ -114,45 +46,27 @@ * This is so that the socket can be used as a key for a dictionary, * so context can be associated with a socket. Using a socket in more * than one thread at the same time is not thread-safe, even if copy * was called to create one "instance" for every thread! */ -@interface OFUDPSocket: OFObject +@interface OFUDPSocket: OFDatagramSocket { - of_socket_t _socket; #ifdef OF_WII uint16_t _port; #endif - bool _blocking; - id _Nullable _delegate; OF_RESERVE_IVARS(4) } -/*! - * @brief Whether the socket is in blocking mode. - * - * By default, a socket is in blocking mode. - */ -@property (nonatomic, getter=isBlocking) bool blocking; - /*! * @brief The delegate for asynchronous operations on the socket. * * @note The delegate is retained for as long as asynchronous operations are - * still outstanding. + * still ongoing. */ @property OF_NULLABLE_PROPERTY (assign, nonatomic) id delegate; -/*! - * @brief Returns a new, autoreleased OFUDPSocket. - * - * @return A new, autoreleased OFUDPSocket - */ -+ (instancetype)socket; - /*! * @brief Binds the socket to the specified host and port. * * @param host The host to bind to. Use `@"0.0.0.0"` for IPv4 or `@"::"` for * IPv6 to bind to all. @@ -160,168 +74,8 @@ * chosen, which can be obtained using the return value. * @return The port the socket was bound to */ - (uint16_t)bindToHost: (OFString *)host port: (uint16_t)port; - -/*! - * @brief Receives a datagram and stores it into the specified buffer. - * - * If the buffer is too small, the datagram is truncated. - * - * @param buffer The buffer to write the datagram to - * @param length The length of the buffer - * @param sender A pointer to an @ref of_socket_address_t, which will be set to - * the address of the sender - * @return The length of the received datagram - */ -- (size_t)receiveIntoBuffer: (void *)buffer - length: (size_t)length - sender: (of_socket_address_t *)sender; - -/*! - * @brief Asynchronously receives a datagram and stores it into the specified - * buffer. - * - * If the buffer is too small, the datagram is truncated. - * - * @param buffer The buffer to write the datagram to - * @param length The length of the buffer - */ -- (void)asyncReceiveIntoBuffer: (void *)buffer - length: (size_t)length; - -/*! - * @brief Asynchronously receives a datagram and stores it into the specified - * buffer. - * - * If the buffer is too small, the datagram is truncated. - * - * @param buffer The buffer to write the datagram to - * @param length The length of the buffer - * @param runLoopMode The run loop mode in which to perform the async receive - */ -- (void)asyncReceiveIntoBuffer: (void *)buffer - length: (size_t)length - runLoopMode: (of_run_loop_mode_t)runLoopMode; - -#ifdef OF_HAVE_BLOCKS -/*! - * @brief Asynchronously receives a datagram and stores it into the specified - * buffer. - * - * If the buffer is too small, the datagram is truncated. - * - * @param buffer The buffer to write the datagram to - * @param length The length of the buffer - * @param block The block to call when the datagram has been received. If the - * block returns true, it will be called again with the same - * buffer and maximum length when more datagrams have been - * received. If you want the next method in the queue to handle - * the datagram received next, you need to return false from the - * method. - */ -- (void)asyncReceiveIntoBuffer: (void *)buffer - length: (size_t)length - block: (of_udp_socket_async_receive_block_t)block; - -/*! - * @brief Asynchronously receives a datagram and stores it into the specified - * buffer. - * - * If the buffer is too small, the datagram is truncated. - * - * @param buffer The buffer to write the datagram to - * @param length The length of the buffer - * @param runLoopMode The run loop mode in which to perform the async receive - * @param block The block to call when the datagram has been received. If the - * block returns true, it will be called again with the same - * buffer and maximum length when more datagrams have been - * received. If you want the next method in the queue to handle - * the datagram received next, you need to return false from the - * method. - */ -- (void)asyncReceiveIntoBuffer: (void *)buffer - length: (size_t)length - runLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_udp_socket_async_receive_block_t)block; -#endif - -/*! - * @brief Sends the specified datagram to the specified address. - * - * @param buffer The buffer to send as a datagram - * @param length The length of the buffer - * @param receiver A pointer to an @ref of_socket_address_t to which the - * datagram should be sent - */ -- (void)sendBuffer: (const void *)buffer - length: (size_t)length - receiver: (const of_socket_address_t *)receiver; - -/*! - * @brief Asynchronously sends the specified datagram to the specified address. - * - * @param data The data to send as a datagram - * @param receiver A pointer to an @ref of_socket_address_t to which the - * datagram should be sent. The receiver is copied. - */ -- (void)asyncSendData: (OFData *)data - receiver: (const of_socket_address_t *)receiver; - -/*! - * @brief Asynchronously sends the specified datagram to the specified address. - * - * @param data The data to send as a datagram - * @param receiver A pointer to an @ref of_socket_address_t to which the - * datagram should be sent. The receiver is copied. - * @param runLoopMode The run loop mode in which to perform the async send - */ -- (void)asyncSendData: (OFData *)data - receiver: (const of_socket_address_t *)receiver - runLoopMode: (of_run_loop_mode_t)runLoopMode; - -#ifdef OF_HAVE_BLOCKS -/*! - * @brief Asynchronously sends the specified datagram to the specified address. - * - * @param data The data to send as a datagram - * @param receiver A pointer to an @ref of_socket_address_t to which the - * datagram should be sent. The receiver is copied. - * @param block The block to call when the packet has been sent. It should - * return the data for the next send with the same callback or nil - * if it should not repeat. - */ -- (void)asyncSendData: (OFData *)data - receiver: (const of_socket_address_t *)receiver - block: (of_udp_socket_async_send_data_block_t)block; - -/*! - * @brief Asynchronously sends the specified datagram to the specified address. - * - * @param data The data to send as a datagram - * @param receiver A pointer to an @ref of_socket_address_t to which the - * datagram should be sent. The receiver is copied. - * @param runLoopMode The run loop mode in which to perform the async send - * @param block The block to call when the packet has been sent. It should - * return the data for the next send with the same callback or nil - * if it should not repeat. - */ -- (void)asyncSendData: (OFData *)data - receiver: (const of_socket_address_t *)receiver - runLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_udp_socket_async_send_data_block_t)block; -#endif - -/*! - * @brief Cancels all pending asynchronous requests on the socket. - */ -- (void)cancelAsyncRequests; - -/*! - * @brief Closes the socket so that it can neither receive nor send any more - * datagrams. - */ -- (void)close; @end OF_ASSUME_NONNULL_END Index: src/OFUDPSocket.m ================================================================== --- src/OFUDPSocket.m +++ src/OFUDPSocket.m @@ -15,118 +15,30 @@ * file. */ #include "config.h" -#include #include -#include #ifdef HAVE_FCNTL_H # include #endif #import "OFUDPSocket.h" #import "OFUDPSocket+Private.h" #import "OFDNSResolver.h" #import "OFData.h" -#import "OFRunLoop+Private.h" -#import "OFRunLoop.h" #import "OFThread.h" #import "OFAlreadyConnectedException.h" #import "OFBindFailedException.h" -#import "OFInitializationFailedException.h" -#import "OFInvalidArgumentException.h" -#import "OFInvalidFormatException.h" -#import "OFNotOpenException.h" -#import "OFOutOfRangeException.h" -#import "OFReadFailedException.h" -#import "OFSetOptionFailedException.h" -#import "OFWriteFailedException.h" #import "socket.h" #import "socket_helpers.h" @implementation OFUDPSocket -@synthesize delegate = _delegate; - -+ (void)initialize -{ - if (self != [OFUDPSocket class]) - return; - - if (!of_socket_init()) - @throw [OFInitializationFailedException - exceptionWithClass: self]; -} - -+ (instancetype)socket -{ - return [[[self alloc] init] autorelease]; -} - -- (instancetype)init -{ - self = [super init]; - - _socket = INVALID_SOCKET; - _blocking = true; - - return self; -} - -- (void)dealloc -{ - if (_socket != INVALID_SOCKET) - [self close]; - - [super dealloc]; -} - -- (id)copy -{ - return [self retain]; -} - -- (bool)isBlocking -{ - return _blocking; -} - -- (void)setBlocking: (bool)enable -{ -#if defined(HAVE_FCNTL) - int flags = fcntl(_socket, F_GETFL, 0); - - if (flags == -1) - @throw [OFSetOptionFailedException exceptionWithObject: self - errNo: errno]; - - if (enable) - flags &= ~O_NONBLOCK; - else - flags |= O_NONBLOCK; - - if (fcntl(_socket, F_SETFL, flags) == -1) - @throw [OFSetOptionFailedException exceptionWithObject: self - errNo: errno]; - - _blocking = enable; -#elif defined(OF_WINDOWS) - u_long v = enable; - - if (ioctlsocket(_socket, FIONBIO, &v) == SOCKET_ERROR) - @throw [OFSetOptionFailedException - exceptionWithObject: self - errNo: of_socket_errno()]; - - _blocking = enable; -#else - OF_UNRECOGNIZED_SELECTOR -#endif -} +@dynamic delegate; - (uint16_t)of_bindToAddress: (of_socket_address_t *)address extraType: (int)extraType { void *pool = objc_autoreleasePoolPush(); @@ -144,15 +56,17 @@ port: port socket: self errNo: of_socket_errno()]; } - _blocking = true; + _canBlock = true; #if SOCK_CLOEXEC == 0 && defined(HAVE_FCNTL) && defined(FD_CLOEXEC) - if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) + /* {} needed to avoid warning with Clang 10 if next #if is false. */ + if ((flags = fcntl(_socket, F_GETFD, 0)) != -1) { fcntl(_socket, F_SETFD, flags | FD_CLOEXEC); + } #endif #if defined(OF_WII) || defined(OF_NINTENDO_3DS) if (of_socket_address_get_port(address) != 0) { #endif @@ -277,235 +191,6 @@ objc_autoreleasePoolPop(pool); return port; } - -- (size_t)receiveIntoBuffer: (void *)buffer - length: (size_t)length - sender: (of_socket_address_t *)sender -{ - ssize_t ret; - - if (_socket == INVALID_SOCKET) - @throw [OFNotOpenException exceptionWithObject: self]; - - sender->length = (socklen_t)sizeof(sender->sockaddr); - -#ifndef OF_WINDOWS - if ((ret = recvfrom(_socket, buffer, length, 0, - &sender->sockaddr.sockaddr, &sender->length)) < 0) - @throw [OFReadFailedException - exceptionWithObject: self - requestedLength: length - errNo: of_socket_errno()]; -#else - if (length > INT_MAX) - @throw [OFOutOfRangeException exception]; - - if ((ret = recvfrom(_socket, buffer, (int)length, 0, - &sender->sockaddr.sockaddr, &sender->length)) < 0) - @throw [OFReadFailedException - exceptionWithObject: self - requestedLength: length - errNo: of_socket_errno()]; -#endif - - switch (sender->sockaddr.sockaddr.sa_family) { - case AF_INET: - sender->family = OF_SOCKET_ADDRESS_FAMILY_IPV4; - break; -#ifdef OF_HAVE_IPV6 - case AF_INET6: - sender->family = OF_SOCKET_ADDRESS_FAMILY_IPV6; - break; -#endif - default: - sender->family = OF_SOCKET_ADDRESS_FAMILY_UNKNOWN; - break; - } - - return ret; -} - -- (void)asyncReceiveIntoBuffer: (void *)buffer - length: (size_t)length -{ - [self asyncReceiveIntoBuffer: buffer - length: length - runLoopMode: of_run_loop_mode_default]; -} - -- (void)asyncReceiveIntoBuffer: (void *)buffer - length: (size_t)length - runLoopMode: (of_run_loop_mode_t)runLoopMode -{ - [OFRunLoop of_addAsyncReceiveForUDPSocket: self - buffer: buffer - length: length - mode: runLoopMode -# ifdef OF_HAVE_BLOCKS - block: NULL -# endif - delegate: _delegate]; -} - -#ifdef OF_HAVE_BLOCKS -- (void)asyncReceiveIntoBuffer: (void *)buffer - length: (size_t)length - block: (of_udp_socket_async_receive_block_t)block -{ - [self asyncReceiveIntoBuffer: buffer - length: length - runLoopMode: of_run_loop_mode_default - block: block]; -} - -- (void)asyncReceiveIntoBuffer: (void *)buffer - length: (size_t)length - runLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_udp_socket_async_receive_block_t)block -{ - [OFRunLoop of_addAsyncReceiveForUDPSocket: self - buffer: buffer - length: length - mode: runLoopMode - block: block - delegate: nil]; -} -#endif - -- (void)sendBuffer: (const void *)buffer - length: (size_t)length - receiver: (const of_socket_address_t *)receiver -{ - if (_socket == INVALID_SOCKET) - @throw [OFNotOpenException exceptionWithObject: self]; - -#ifndef OF_WINDOWS - ssize_t bytesWritten; - - if (length > SSIZE_MAX) - @throw [OFOutOfRangeException exception]; - - if ((bytesWritten = sendto(_socket, (void *)buffer, length, 0, - (struct sockaddr *)&receiver->sockaddr.sockaddr, - receiver->length)) < 0) - @throw [OFWriteFailedException - exceptionWithObject: self - requestedLength: length - bytesWritten: 0 - errNo: of_socket_errno()]; -#else - int bytesWritten; - - if (length > INT_MAX) - @throw [OFOutOfRangeException exception]; - - if ((bytesWritten = sendto(_socket, buffer, (int)length, 0, - &receiver->sockaddr.sockaddr, receiver->length)) < 0) - @throw [OFWriteFailedException - exceptionWithObject: self - requestedLength: length - bytesWritten: 0 - errNo: of_socket_errno()]; -#endif - - if ((size_t)bytesWritten != length) - @throw [OFWriteFailedException exceptionWithObject: self - requestedLength: length - bytesWritten: bytesWritten - errNo: 0]; -} - -- (void)asyncSendData: (OFData *)data - receiver: (const of_socket_address_t *)receiver -{ - [self asyncSendData: data - receiver: receiver - runLoopMode: of_run_loop_mode_default]; -} - -- (void)asyncSendData: (OFData *)data - receiver: (const of_socket_address_t *)receiver - runLoopMode: (of_run_loop_mode_t)runLoopMode -{ - [OFRunLoop of_addAsyncSendForUDPSocket: self - data: data - receiver: receiver - mode: runLoopMode -# ifdef OF_HAVE_BLOCKS - block: NULL -# endif - delegate: _delegate]; -} - -#ifdef OF_HAVE_BLOCKS -- (void)asyncSendData: (OFData *)data - receiver: (const of_socket_address_t *)receiver - block: (of_udp_socket_async_send_data_block_t)block -{ - [self asyncSendData: data - receiver: receiver - runLoopMode: of_run_loop_mode_default - block: block]; -} - -- (void)asyncSendData: (OFData *)data - receiver: (const of_socket_address_t *)receiver - runLoopMode: (of_run_loop_mode_t)runLoopMode - block: (of_udp_socket_async_send_data_block_t)block -{ - [OFRunLoop of_addAsyncSendForUDPSocket: self - data: data - receiver: receiver - mode: runLoopMode - block: block - delegate: nil]; -} -#endif - -- (void)cancelAsyncRequests -{ - [OFRunLoop of_cancelAsyncRequestsForObject: self - mode: of_run_loop_mode_default]; -} - -- (int)fileDescriptorForReading -{ -#ifndef OF_WINDOWS - return _socket; -#else - if (_socket == INVALID_SOCKET) - return -1; - - if (_socket > INT_MAX) - @throw [OFOutOfRangeException exception]; - - return (int)_socket; -#endif -} - -- (int)fileDescriptorForWriting -{ -#ifndef OF_WINDOWS - return _socket; -#else - if (_socket == INVALID_SOCKET) - return -1; - - if (_socket > INT_MAX) - @throw [OFOutOfRangeException exception]; - - return (int)_socket; -#endif -} - -- (void)close -{ - if (_socket == INVALID_SOCKET) - @throw [OFNotOpenException exceptionWithObject: self]; - - closesocket(_socket); - _socket = INVALID_SOCKET; -} @end Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -32,44 +32,70 @@ #endif #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFOutOfMemoryException.h" + +#import "once.h" + +@interface OFURLAllowedCharacterSetBase: OFCharacterSet +@end + +@interface OFURLAllowedCharacterSet: OFURLAllowedCharacterSetBase +@end + +@interface OFURLSchemeAllowedCharacterSet: OFURLAllowedCharacterSetBase +@end + +@interface OFURLPathAllowedCharacterSet: OFURLAllowedCharacterSetBase +@end + +@interface OFURLQueryOrFragmentAllowedCharacterSet: OFURLAllowedCharacterSetBase +@end static OFCharacterSet *URLAllowedCharacterSet = nil; static OFCharacterSet *URLSchemeAllowedCharacterSet = nil; static OFCharacterSet *URLPathAllowedCharacterSet = nil; static OFCharacterSet *URLQueryOrFragmentAllowedCharacterSet = nil; -@interface OFURLAllowedCharacterSetBase: OFCharacterSet -- (instancetype)of_init OF_METHOD_FAMILY(init); -@end - -@interface OFURLAllowedCharacterSet: OFURLAllowedCharacterSetBase -+ (OFCharacterSet *)URLAllowedCharacterSet; -@end - -@interface OFURLSchemeAllowedCharacterSet: OFURLAllowedCharacterSetBase -+ (OFCharacterSet *)URLSchemeAllowedCharacterSet; -@end - -@interface OFURLPathAllowedCharacterSet: OFURLAllowedCharacterSetBase -+ (OFCharacterSet *)URLPathAllowedCharacterSet; -@end - -@interface OFURLQueryOrFragmentAllowedCharacterSet: OFURLAllowedCharacterSetBase -+ (OFCharacterSet *)URLQueryOrFragmentAllowedCharacterSet; -@end +static of_once_t URLAllowedCharacterSetOnce = OF_ONCE_INIT; +static of_once_t URLQueryOrFragmentAllowedCharacterSetOnce = OF_ONCE_INIT; + +static void +initURLAllowedCharacterSet(void) +{ + URLAllowedCharacterSet = [[OFURLAllowedCharacterSet alloc] init]; +} + +static void +initURLSchemeAllowedCharacterSet(void) +{ + URLSchemeAllowedCharacterSet = + [[OFURLSchemeAllowedCharacterSet alloc] init]; +} + +static void +initURLPathAllowedCharacterSet(void) +{ + URLPathAllowedCharacterSet = + [[OFURLPathAllowedCharacterSet alloc] init]; +} + +static void +initURLQueryOrFragmentAllowedCharacterSet(void) +{ + URLQueryOrFragmentAllowedCharacterSet = + [[OFURLQueryOrFragmentAllowedCharacterSet alloc] init]; +} @interface OFInvertedCharacterSetWithoutPercent: OFCharacterSet { OFCharacterSet *_characterSet; bool (*_characterIsMember)(id, SEL, of_unichar_t); } -- (instancetype)of_initWithCharacterSet: (OFCharacterSet *)characterSet - OF_METHOD_FAMILY(init); +- (instancetype)initWithCharacterSet: (OFCharacterSet *)characterSet; @end bool of_url_is_ipv6_host(OFString *host) { @@ -90,20 +116,10 @@ return hasColon; } @implementation OFURLAllowedCharacterSetBase -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)of_init -{ - return [super init]; -} - - (instancetype)autorelease { return self; } @@ -121,23 +137,10 @@ return OF_RETAIN_COUNT_MAX; } @end @implementation OFURLAllowedCharacterSet -+ (void)initialize -{ - if (self != [OFURLAllowedCharacterSet class]) - return; - - URLAllowedCharacterSet = [[OFURLAllowedCharacterSet alloc] of_init]; -} - -+ (OFCharacterSet *)URLAllowedCharacterSet -{ - return URLAllowedCharacterSet; -} - - (bool)characterIsMember: (of_unichar_t)character { if (character < CHAR_MAX && of_ascii_isalnum(character)) return true; @@ -163,24 +166,10 @@ } } @end @implementation OFURLSchemeAllowedCharacterSet -+ (void)initialize -{ - if (self != [OFURLSchemeAllowedCharacterSet class]) - return; - - URLSchemeAllowedCharacterSet = - [[OFURLSchemeAllowedCharacterSet alloc] of_init]; -} - -+ (OFCharacterSet *)URLSchemeAllowedCharacterSet -{ - return URLSchemeAllowedCharacterSet; -} - - (bool)characterIsMember: (of_unichar_t)character { if (character < CHAR_MAX && of_ascii_isalnum(character)) return true; @@ -194,24 +183,10 @@ } } @end @implementation OFURLPathAllowedCharacterSet -+ (void)initialize -{ - if (self != [OFURLPathAllowedCharacterSet class]) - return; - - URLPathAllowedCharacterSet = - [[OFURLPathAllowedCharacterSet alloc] of_init]; -} - -+ (OFCharacterSet *)URLPathAllowedCharacterSet -{ - return URLPathAllowedCharacterSet; -} - - (bool)characterIsMember: (of_unichar_t)character { if (character < CHAR_MAX && of_ascii_isalnum(character)) return true; @@ -240,24 +215,10 @@ } } @end @implementation OFURLQueryOrFragmentAllowedCharacterSet -+ (void)initialize -{ - if (self != [OFURLQueryOrFragmentAllowedCharacterSet class]) - return; - - URLQueryOrFragmentAllowedCharacterSet = - [[OFURLQueryOrFragmentAllowedCharacterSet alloc] of_init]; -} - -+ (OFCharacterSet *)URLQueryOrFragmentAllowedCharacterSet -{ - return URLQueryOrFragmentAllowedCharacterSet; -} - - (bool)characterIsMember: (of_unichar_t)character { if (character < CHAR_MAX && of_ascii_isalnum(character)) return true; @@ -287,22 +248,23 @@ } } @end @implementation OFInvertedCharacterSetWithoutPercent -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)of_initWithCharacterSet: (OFCharacterSet *)characterSet +- (instancetype)initWithCharacterSet: (OFCharacterSet *)characterSet { self = [super init]; - _characterSet = [characterSet retain]; - _characterIsMember = (bool (*)(id, SEL, of_unichar_t)) - [_characterSet methodForSelector: @selector(characterIsMember:)]; + @try { + _characterSet = [characterSet retain]; + _characterIsMember = (bool (*)(id, SEL, of_unichar_t)) + [_characterSet methodForSelector: + @selector(characterIsMember:)]; + } @catch (id e) { + [self release]; + @throw e; + } return self; } - (void)dealloc @@ -323,11 +285,11 @@ of_url_verify_escaped(OFString *string, OFCharacterSet *characterSet) { void *pool = objc_autoreleasePoolPush(); characterSet = [[[OFInvertedCharacterSetWithoutPercent alloc] - of_initWithCharacterSet: characterSet] autorelease]; + initWithCharacterSet: characterSet] autorelease]; if ([string indexOfCharacterFromSet: characterSet] != OF_NOT_FOUND) @throw [OFInvalidFormatException exception]; objc_autoreleasePoolPop(pool); @@ -334,43 +296,59 @@ } @implementation OFCharacterSet (URLCharacterSets) + (OFCharacterSet *)URLSchemeAllowedCharacterSet { - return [OFURLSchemeAllowedCharacterSet URLSchemeAllowedCharacterSet]; + static of_once_t onceControl = OF_ONCE_INIT; + of_once(&onceControl, initURLSchemeAllowedCharacterSet); + + return URLSchemeAllowedCharacterSet; } + (OFCharacterSet *)URLHostAllowedCharacterSet { - return [OFURLAllowedCharacterSet URLAllowedCharacterSet]; + of_once(&URLAllowedCharacterSetOnce, initURLAllowedCharacterSet); + + return URLAllowedCharacterSet; } + (OFCharacterSet *)URLUserAllowedCharacterSet { - return [OFURLAllowedCharacterSet URLAllowedCharacterSet]; + of_once(&URLAllowedCharacterSetOnce, initURLAllowedCharacterSet); + + return URLAllowedCharacterSet; } + (OFCharacterSet *)URLPasswordAllowedCharacterSet { - return [OFURLAllowedCharacterSet URLAllowedCharacterSet]; + of_once(&URLAllowedCharacterSetOnce, initURLAllowedCharacterSet); + + return URLAllowedCharacterSet; } + (OFCharacterSet *)URLPathAllowedCharacterSet { - return [OFURLPathAllowedCharacterSet URLPathAllowedCharacterSet]; + static of_once_t onceControl = OF_ONCE_INIT; + of_once(&onceControl, initURLPathAllowedCharacterSet); + + return URLPathAllowedCharacterSet; } + (OFCharacterSet *)URLQueryAllowedCharacterSet { - return [OFURLQueryOrFragmentAllowedCharacterSet - URLQueryOrFragmentAllowedCharacterSet]; + of_once(&URLQueryOrFragmentAllowedCharacterSetOnce, + initURLQueryOrFragmentAllowedCharacterSet); + + return URLQueryOrFragmentAllowedCharacterSet; } + (OFCharacterSet *)URLFragmentAllowedCharacterSet { - return [OFURLQueryOrFragmentAllowedCharacterSet - URLQueryOrFragmentAllowedCharacterSet]; + of_once(&URLQueryOrFragmentAllowedCharacterSetOnce, + initURLQueryOrFragmentAllowedCharacterSet); + + return URLQueryOrFragmentAllowedCharacterSet; } @end @implementation OFURL + (instancetype)URL Index: src/OFWin32ConsoleStdIOStream.h ================================================================== --- src/OFWin32ConsoleStdIOStream.h +++ src/OFWin32ConsoleStdIOStream.h @@ -22,12 +22,13 @@ OF_ASSUME_NONNULL_BEGIN @interface OFWin32ConsoleStdIOStream: OFStdIOStream { HANDLE _handle; + WORD _attributes; of_char16_t _incompleteUTF16Surrogate; char _incompleteUTF8Surrogate[4]; size_t _incompleteUTF8SurrogateLen; } @end OF_ASSUME_NONNULL_END Index: src/OFWin32ConsoleStdIOStream.m ================================================================== --- src/OFWin32ConsoleStdIOStream.m +++ src/OFWin32ConsoleStdIOStream.m @@ -46,21 +46,42 @@ #include #include #include #import "OFWin32ConsoleStdIOStream.h" +#import "OFColor.h" #import "OFData.h" #import "OFStdIOStream+Private.h" #import "OFString.h" +#import "OFSystemInfo.h" #import "OFInvalidArgumentException.h" #import "OFInvalidEncodingException.h" #import "OFOutOfRangeException.h" #import "OFReadFailedException.h" #import "OFWriteFailedException.h" #include + +static of_string_encoding_t +codepageToEncoding(UINT codepage) +{ + switch (codepage) { + case 437: + return OF_STRING_ENCODING_CODEPAGE_437; + case 850: + return OF_STRING_ENCODING_CODEPAGE_850; + case 858: + return OF_STRING_ENCODING_CODEPAGE_858; + case 1251: + return OF_STRING_ENCODING_WINDOWS_1251; + case 1252: + return OF_STRING_ENCODING_WINDOWS_1252; + default: + @throw [OFInvalidEncodingException exception]; + } +} @implementation OFWin32ConsoleStdIOStream + (void)load { int fd; @@ -83,18 +104,22 @@ { self = [super of_initWithFileDescriptor: fd]; @try { DWORD mode; + CONSOLE_SCREEN_BUFFER_INFO csbi; _handle = (HANDLE)_get_osfhandle(fd); if (_handle == INVALID_HANDLE_VALUE) @throw [OFInvalidArgumentException exception]; /* Not a console: Treat it as a regular OFStdIOStream */ if (!GetConsoleMode(_handle, &mode)) object_setClass(self, [OFStdIOStream class]); + + if (GetConsoleScreenBufferInfo(_handle, &csbi)) + _attributes = csbi.wAttributes; } @catch (id e) { [self release]; @throw e; } @@ -107,26 +132,51 @@ void *pool = objc_autoreleasePoolPush(); char *buffer = buffer_; of_char16_t *UTF16; size_t j = 0; - if (length > sizeof(UINT32_MAX)) + if (length > UINT32_MAX) @throw [OFOutOfRangeException exception]; UTF16 = [self allocMemoryWithSize: sizeof(of_char16_t) count: length]; @try { DWORD UTF16Len; OFMutableData *rest = nil; size_t i = 0; - if (!ReadConsoleW(_handle, UTF16, (DWORD)length, &UTF16Len, - NULL)) - @throw [OFReadFailedException - exceptionWithObject: self - requestedLength: length * 2 - errNo: EIO]; + if ([OFSystemInfo isWindowsNT]) { + if (!ReadConsoleW(_handle, UTF16, (DWORD)length, + &UTF16Len, NULL)) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: length * 2 + errNo: EIO]; + } else { + of_string_encoding_t encoding; + OFString *string; + size_t stringLen; + + if (!ReadConsoleA(_handle, (char *)UTF16, (DWORD)length, + &UTF16Len, NULL)) + @throw [OFReadFailedException + exceptionWithObject: self + requestedLength: length + errNo: EIO]; + + encoding = codepageToEncoding(GetConsoleCP()); + string = [OFString stringWithCString: (char *)UTF16 + encoding: encoding + length: UTF16Len]; + stringLen = string.UTF16StringLength; + + if (stringLen > length) + @throw [OFOutOfRangeException exception]; + + UTF16Len = (DWORD)stringLen; + memcpy(UTF16, string.UTF16String, stringLen); + } if (UTF16Len > 0 && _incompleteUTF16Surrogate != 0) { of_unichar_t c = (((_incompleteUTF16Surrogate & 0x3FF) << 10) | (UTF16[0] & 0x3FF)) + 0x10000; @@ -269,17 +319,42 @@ UTF16[0] = c; UTF16Len = 1; } } - if (!WriteConsoleW(_handle, UTF16, UTF16Len, &bytesWritten, - NULL)) - @throw [OFWriteFailedException - exceptionWithObject: self - requestedLength: UTF16Len * 2 - bytesWritten: 0 - errNo: EIO]; + if ([OFSystemInfo isWindowsNT]) { + if (!WriteConsoleW(_handle, UTF16, UTF16Len, + &bytesWritten, NULL)) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: UTF16Len * 2 + bytesWritten: bytesWritten * 2 + errNo: EIO]; + } else { + void *pool = objc_autoreleasePoolPush(); + OFString *string = [OFString + stringWithUTF16String: UTF16 + length: UTF16Len]; + of_string_encoding_t encoding = + codepageToEncoding(GetConsoleOutputCP()); + size_t nativeLen = [string + cStringLengthWithEncoding: encoding]; + + if (nativeLen > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + if (!WriteConsoleA(_handle, + [string cStringWithEncoding: encoding], + (DWORD)nativeLen, &bytesWritten, NULL)) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: nativeLen + bytesWritten: bytesWritten + errNo: EIO]; + + objc_autoreleasePoolPop(pool); + } if (bytesWritten != UTF16Len) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: UTF16Len * 2 @@ -329,16 +404,41 @@ } if (j > UINT32_MAX) @throw [OFOutOfRangeException exception]; - if (!WriteConsoleW(_handle, tmp, (DWORD)j, &bytesWritten, NULL)) - @throw [OFWriteFailedException - exceptionWithObject: self - requestedLength: j * 2 - bytesWritten: 0 - errNo: EIO]; + if ([OFSystemInfo isWindowsNT]) { + if (!WriteConsoleW(_handle, tmp, (DWORD)j, + &bytesWritten, NULL)) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: j * 2 + bytesWritten: bytesWritten * 2 + errNo: EIO]; + } else { + void *pool = objc_autoreleasePoolPush(); + OFString *string = [OFString stringWithUTF16String: tmp + length: j]; + of_string_encoding_t encoding = + codepageToEncoding(GetConsoleOutputCP()); + size_t nativeLen = [string + cStringLengthWithEncoding: encoding]; + + if (nativeLen > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + if (!WriteConsoleA(_handle, + [string cStringWithEncoding: encoding], + (DWORD)nativeLen, &bytesWritten, NULL)) + @throw [OFWriteFailedException + exceptionWithObject: self + requestedLength: nativeLen + bytesWritten: bytesWritten + errNo: EIO]; + + objc_autoreleasePoolPop(pool); + } if (bytesWritten != j) @throw [OFWriteFailedException exceptionWithObject: self requestedLength: j * 2 @@ -353,6 +453,168 @@ * since any incomplete write is an exception here anyway, we can just * return length. */ return length; } + +- (bool)hasTerminal +{ + /* + * We can never get here if there is no terminal, as the initializer + * changes the class to OFStdIOStream in that case. + */ + return true; +} + +- (int)columns +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (!GetConsoleScreenBufferInfo(_handle, &csbi)) + return -1; + + return csbi.dwSize.X; +} + +- (int)rows +{ + /* + * The buffer size returned is almost always larger than the window + * size, so this is useless. + */ + return -1; +} + +- (void)setForegroundColor: (OFColor *)color +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + float red, green, blue; + + if (!GetConsoleScreenBufferInfo(_handle, &csbi)) + return; + + csbi.wAttributes &= ~(FOREGROUND_RED | FOREGROUND_GREEN | + FOREGROUND_BLUE | FOREGROUND_INTENSITY); + + [color getRed: &red + green: &green + blue: &blue + alpha: NULL]; + + if (red >= 0.25) + csbi.wAttributes |= FOREGROUND_RED; + if (green >= 0.25) + csbi.wAttributes |= FOREGROUND_GREEN; + if (blue >= 0.25) + csbi.wAttributes |= FOREGROUND_BLUE; + + if (red >= 0.75 || green >= 0.75 || blue >= 0.75) + csbi.wAttributes |= FOREGROUND_INTENSITY; + + SetConsoleTextAttribute(_handle, csbi.wAttributes); +} + +- (void)setBackgroundColor: (OFColor *)color +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + float red, green, blue; + + if (!GetConsoleScreenBufferInfo(_handle, &csbi)) + return; + + csbi.wAttributes &= ~(BACKGROUND_RED | BACKGROUND_GREEN | + BACKGROUND_BLUE | BACKGROUND_INTENSITY); + + [color getRed: &red + green: &green + blue: &blue + alpha: NULL]; + + if (red >= 0.25) + csbi.wAttributes |= BACKGROUND_RED; + if (green >= 0.25) + csbi.wAttributes |= BACKGROUND_GREEN; + if (blue >= 0.25) + csbi.wAttributes |= BACKGROUND_BLUE; + + if (red >= 0.75 || green >= 0.75 || blue >= 0.75) + csbi.wAttributes |= BACKGROUND_INTENSITY; + + SetConsoleTextAttribute(_handle, csbi.wAttributes); +} + +- (void)reset +{ + SetConsoleTextAttribute(_handle, _attributes); +} + +- (void)clear +{ + static COORD zero = { 0, 0 }; + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD bytesWritten; + + if (!GetConsoleScreenBufferInfo(_handle, &csbi)) + return; + + if (!FillConsoleOutputCharacter(_handle, ' ', + csbi.dwSize.X * csbi.dwSize.Y, zero, &bytesWritten)) + return; + + if (!FillConsoleOutputAttribute(_handle, csbi.wAttributes, + csbi.dwSize.X * csbi.dwSize.Y, zero, &bytesWritten)) + return; + + SetConsoleCursorPosition(_handle, zero); +} + +- (void)eraseLine +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD bytesWritten; + + if (!GetConsoleScreenBufferInfo(_handle, &csbi)) + return; + + csbi.dwCursorPosition.X = 0; + + if (!FillConsoleOutputCharacter(_handle, ' ', csbi.dwSize.X, + csbi.dwCursorPosition, &bytesWritten)) + return; + + FillConsoleOutputAttribute(_handle, csbi.wAttributes, csbi.dwSize.X, + csbi.dwCursorPosition, &bytesWritten); +} + +- (void)setCursorColumn: (unsigned int)column +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (!GetConsoleScreenBufferInfo(_handle, &csbi)) + return; + + csbi.dwCursorPosition.X = column; + + SetConsoleCursorPosition(_handle, csbi.dwCursorPosition); +} + +- (void)setCursorPosition: (of_point_t)position +{ + if (position.x < 0 || position.y < 0) + @throw [OFInvalidArgumentException exception]; + + SetConsoleCursorPosition(_handle, (COORD){ position.x, position.y }); +} + +- (void)setRelativeCursorPosition: (of_point_t)position +{ + CONSOLE_SCREEN_BUFFER_INFO csbi; + + if (!GetConsoleScreenBufferInfo(_handle, &csbi)) + return; + + csbi.dwCursorPosition.X += position.x; + csbi.dwCursorPosition.Y += position.y; + + SetConsoleCursorPosition(_handle, csbi.dwCursorPosition); +} @end Index: src/OFWindowsRegistryKey.h ================================================================== --- src/OFWindowsRegistryKey.h +++ src/OFWindowsRegistryKey.h @@ -130,26 +130,22 @@ */ - (OFWindowsRegistryKey *) createSubkeyAtPath: (OFString *)path options: (DWORD)options securityAndAccessRights: (REGSAM)securityAndAccessRights - securityAttributes: (nullable LPSECURITY_ATTRIBUTES)securityAttributes - disposition: (nullable LPDWORD)disposition; + securityAttributes: (nullable SECURITY_ATTRIBUTES *)securityAttributes + disposition: (nullable DWORD *)disposition; /*! * @brief Returns the data for the specified value at the specified path. * * @param value The name of the value to return - * @param subkeyPath The path of the key from which to retrieve the value - * @param flags Extra flags for `RegGetValue()`. Usually 0. * @param type A pointer to store the type of the value, or NULL * @return The data for the specified value */ - (nullable OFData *)dataForValue: (nullable OFString *)value - subkeyPath: (nullable OFString *)subkeyPath - flags: (DWORD)flags - type: (nullable LPDWORD)type; + type: (nullable DWORD *)type; /*! * @brief Sets the data for the specified value. * * @param data The data to set the value to @@ -162,29 +158,23 @@ /*! * @brief Returns the string for the specified value at the specified path. * * @param value The name of the value to return - * @param subkeyPath The path of the key from which to retrieve the value * @return The string for the specified value */ -- (nullable OFString *)stringForValue: (nullable OFString *)value - subkeyPath: (nullable OFString *)subkeyPath; +- (nullable OFString *)stringForValue: (nullable OFString *)value; /*! * @brief Returns the string for the specified value at the specified path. * * @param value The name of the value to return - * @param subkeyPath The path of the key from which to retrieve the value - * @param flags Extra flags for `RegGetValue()`. Usually 0. * @param type A pointer to store the type of the value, or NULL * @return The string for the specified value */ - (nullable OFString *)stringForValue: (nullable OFString *)value - subkeyPath: (nullable OFString *)subkeyPath - flags: (DWORD)flags - type: (nullable LPDWORD)type; + type: (nullable DWORD *)type; /*! * @brief Sets the string for the specified value. * * @param string The string to set the value to Index: src/OFWindowsRegistryKey.m ================================================================== --- src/OFWindowsRegistryKey.m +++ src/OFWindowsRegistryKey.m @@ -17,17 +17,20 @@ #include "config.h" #import "OFWindowsRegistryKey.h" #import "OFData.h" +#import "OFLocale.h" +#import "OFSystemInfo.h" #include #import "OFCreateWindowsRegistryKeyFailedException.h" #import "OFDeleteWindowsRegistryKeyFailedException.h" #import "OFDeleteWindowsRegistryValueFailedException.h" #import "OFGetWindowsRegistryValueFailedException.h" +#import "OFInvalidEncodingException.h" #import "OFInvalidFormatException.h" #import "OFOpenWindowsRegistryKeyFailedException.h" #import "OFOutOfRangeException.h" #import "OFSetWindowsRegistryValueFailedException.h" @@ -105,12 +108,19 @@ { void *pool = objc_autoreleasePoolPush(); LSTATUS status; HKEY subKey; - if ((status = RegOpenKeyExW(_hKey, path.UTF16String, options, - securityAndAccessRights, &subKey)) != ERROR_SUCCESS) { + if ([OFSystemInfo isWindowsNT]) + status = RegOpenKeyExW(_hKey, path.UTF16String, options, + securityAndAccessRights, &subKey); + else + status = RegOpenKeyExA(_hKey, + [path cStringWithEncoding: [OFLocale encoding]], options, + securityAndAccessRights, &subKey); + + if (status != ERROR_SUCCESS) { if (status == ERROR_FILE_NOT_FOUND) { objc_autoreleasePoolPop(pool); return nil; } @@ -142,19 +152,27 @@ - (OFWindowsRegistryKey *) createSubkeyAtPath: (OFString *)path options: (DWORD)options securityAndAccessRights: (REGSAM)securityAndAccessRights securityAttributes: (LPSECURITY_ATTRIBUTES)securityAttributes - disposition: (LPDWORD)disposition + disposition: (DWORD *)disposition { void *pool = objc_autoreleasePoolPush(); LSTATUS status; HKEY subKey; - if ((status = RegCreateKeyExW(_hKey, path.UTF16String, 0, - NULL, options, securityAndAccessRights, securityAttributes, - &subKey, NULL)) != ERROR_SUCCESS) + if ([OFSystemInfo isWindowsNT]) + status = RegCreateKeyExW(_hKey, path.UTF16String, 0, + NULL, options, securityAndAccessRights, securityAttributes, + &subKey, NULL); + else + status = RegCreateKeyExA(_hKey, + [path cStringWithEncoding: [OFLocale encoding]], 0, NULL, + options, securityAndAccessRights, securityAttributes, + &subKey, NULL); + + if (status != ERROR_SUCCESS) @throw [OFCreateWindowsRegistryKeyFailedException exceptionWithRegistryKey: self path: path options: options securityAndAccessRights: securityAndAccessRights @@ -167,23 +185,27 @@ close: true] autorelease]; } - (OFData *)dataForValue: (OFString *)value - subkeyPath: (OFString *)subkeyPath - flags: (DWORD)flags - type: (LPDWORD)type + type: (DWORD *)type { void *pool = objc_autoreleasePoolPush(); - char stackBuffer[256], *buffer = stackBuffer; + BYTE stackBuffer[256], *buffer = stackBuffer; DWORD length = sizeof(stackBuffer); OFMutableData *ret = nil; + bool winNT = [OFSystemInfo isWindowsNT]; LSTATUS status; for (;;) { - status = RegGetValueW(_hKey, subkeyPath.UTF16String, - value.UTF16String, flags, type, buffer, &length); + if (winNT) + status = RegQueryValueExW(_hKey, value.UTF16String, + NULL, type, buffer, &length); + else + status = RegQueryValueExA(_hKey, + [value cStringWithEncoding: [OFLocale encoding]], + NULL, type, buffer, &length); switch (status) { case ERROR_SUCCESS: if (buffer == stackBuffer) { objc_autoreleasePoolPop(pool); @@ -213,12 +235,10 @@ continue; default: @throw [OFGetWindowsRegistryValueFailedException exceptionWithRegistryKey: self value: value - subkeyPath: subkeyPath - flags: flags status: status]; } } } @@ -230,67 +250,95 @@ LSTATUS status; if (length > UINT32_MAX) @throw [OFOutOfRangeException exception]; - if ((status = RegSetValueExW(_hKey, value.UTF16String, 0, type, - data.items, (DWORD)length)) != ERROR_SUCCESS) + if ([OFSystemInfo isWindowsNT]) + status = RegSetValueExW(_hKey, value.UTF16String, 0, type, + data.items, (DWORD)length); + else + status = RegSetValueExA(_hKey, + [value cStringWithEncoding: [OFLocale encoding]], 0, type, + data.items, (DWORD)length); + + if (status != ERROR_SUCCESS) @throw [OFSetWindowsRegistryValueFailedException exceptionWithRegistryKey: self value: value data: data type: type status: status]; } - (OFString *)stringForValue: (OFString *)value - subkeyPath: (OFString *)subkeyPath { return [self stringForValue: value - subkeyPath: subkeyPath - flags: RRF_RT_REG_SZ type: NULL]; } - (OFString *)stringForValue: (OFString *)value - subkeyPath: (OFString *)subkeyPath - flags: (DWORD)flags - type: (LPDWORD)type + type: (DWORD *)typeOut { void *pool = objc_autoreleasePoolPush(); + DWORD type; OFData *data = [self dataForValue: value - subkeyPath: subkeyPath - flags: flags - type: type]; - const of_char16_t *UTF16String; - size_t length; + type: &type]; OFString *ret; if (data == nil) return nil; - UTF16String = data.items; - length = data.count; + if (type != REG_SZ && type != REG_EXPAND_SZ && type != REG_LINK) + @throw [OFInvalidEncodingException exception]; - if (data.itemSize != 1 || length % 2 == 1) + if (data.itemSize != 1) @throw [OFInvalidFormatException exception]; - length /= 2; - - /* - * REG_SZ and REG_EXPAND_SZ contain a \0, but can contain data after it - * that should be ignored. - */ - for (size_t i = 0; i < length; i++) { - if (UTF16String[i] == 0) { - length = i; - break; - } - } - - ret = [[OFString alloc] initWithUTF16String: UTF16String - length: length]; + if ([OFSystemInfo isWindowsNT]) { + const of_char16_t *UTF16String = data.items; + size_t length = data.count; + + if (length % 2 == 1) + @throw [OFInvalidFormatException exception]; + + length /= 2; + + /* + * REG_SZ and REG_EXPAND_SZ contain a \0, but can contain data + * after it that should be ignored. + */ + for (size_t i = 0; i < length; i++) { + if (UTF16String[i] == 0) { + length = i; + break; + } + } + + ret = [[OFString alloc] initWithUTF16String: UTF16String + length: length]; + } else { + const char *cString = data.items; + size_t length = data.count; + + /* + * REG_SZ and REG_EXPAND_SZ contain a \0, but can contain data + * after it that should be ignored. + */ + for (size_t i = 0; i < length; i++) { + if (cString[i] == 0) { + length = i; + break; + } + } + + ret = [[OFString alloc] initWithCString: cString + encoding: [OFLocale encoding] + length: length]; + } + + if (typeOut != NULL) + *typeOut = type; objc_autoreleasePoolPop(pool); return [ret autorelease]; } @@ -308,13 +356,23 @@ type: (DWORD)type { void *pool = objc_autoreleasePoolPush(); OFData *data; - data = [OFData dataWithItems: string.UTF16String - itemSize: sizeof(of_char16_t) - count: string.UTF16StringLength + 1]; + if ([OFSystemInfo isWindowsNT]) + data = [OFData dataWithItems: string.UTF16String + itemSize: sizeof(of_char16_t) + count: string.UTF16StringLength + 1]; + else { + of_string_encoding_t encoding = [OFLocale encoding]; + const char *cString = [string cStringWithEncoding: encoding]; + size_t length = [string cStringLengthWithEncoding: encoding]; + + data = [OFData dataWithItems: cString + count: length + 1]; + } + [self setData: data forValue: value type: type]; objc_autoreleasePoolPop(pool); @@ -323,12 +381,17 @@ - (void)deleteValue: (OFString *)value { void *pool = objc_autoreleasePoolPush(); LSTATUS status; - if ((status = RegDeleteValueW(_hKey, value.UTF16String)) != - ERROR_SUCCESS) + if ([OFSystemInfo isWindowsNT]) + status = RegDeleteValueW(_hKey, value.UTF16String); + else + status = RegDeleteValueA(_hKey, + [value cStringWithEncoding: [OFLocale encoding]]); + + if (status != ERROR_SUCCESS) @throw [OFDeleteWindowsRegistryValueFailedException exceptionWithRegistryKey: self value: value status: status]; @@ -338,15 +401,20 @@ - (void)deleteSubkeyAtPath: (OFString *)subkeyPath { void *pool = objc_autoreleasePoolPush(); LSTATUS status; - if ((status = RegDeleteKeyW(_hKey, subkeyPath.UTF16String)) != - ERROR_SUCCESS) + if ([OFSystemInfo isWindowsNT]) + status = RegDeleteKeyW(_hKey, subkeyPath.UTF16String); + else + status = RegDeleteKeyA(_hKey, + [subkeyPath cStringWithEncoding: [OFLocale encoding]]); + + if (status != ERROR_SUCCESS) @throw [OFDeleteWindowsRegistryKeyFailedException exceptionWithRegistryKey: self subkeyPath: subkeyPath status: status]; objc_autoreleasePoolPop(pool); } @end Index: src/OFXMLParser.m ================================================================== --- src/OFXMLParser.m +++ src/OFXMLParser.m @@ -32,10 +32,12 @@ # import "OFFile.h" #endif #import "OFSystemInfo.h" #import "OFInitializationFailedException.h" +#import "OFInvalidArgumentException.h" +#import "OFInvalidEncodingException.h" #import "OFInvalidFormatException.h" #import "OFMalformedXMLException.h" #import "OFOutOfRangeException.h" #import "OFUnboundPrefixException.h" @@ -478,12 +480,19 @@ return false; hasVersion = true; } - if ([attribute isEqual: @"encoding"]) - _encoding = of_string_parse_encoding(value); + if ([attribute isEqual: @"encoding"]) { + @try { + _encoding = + of_string_parse_encoding(value); + } @catch (OFInvalidArgumentException *e) { + @throw [OFInvalidEncodingException + exception]; + } + } last = i + 1; PIState = 0; break; Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -76,10 +76,18 @@ # import "OFKernelEventObserver.h" # import "OFDNSQuery.h" # import "OFDNSResourceRecord.h" # import "OFDNSResponse.h" # import "OFDNSResolver.h" +# ifdef OF_HAVE_IPX +# import "OFIPXSocket.h" +# import "OFSPXSocket.h" +# import "OFSPXStreamSocket.h" +# endif +# ifdef OF_HAVE_SCTP +# import "OFSCTPSocket.h" +# endif #endif #ifdef OF_HAVE_SOCKETS # ifdef OF_HAVE_THREADS # import "OFHTTPClient.h" # endif Index: src/block.h ================================================================== --- src/block.h +++ src/block.h @@ -13,11 +13,14 @@ * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ -#import "macros.h" +#ifndef OBJFW_BLOCK_H +#define OBJFW_BLOCK_H + +#include "macros.h" OF_ASSUME_NONNULL_BEGIN typedef struct of_block_literal_t { #ifdef __OBJC__ @@ -68,5 +71,7 @@ #ifndef Block_release # define Block_release(...) _Block_release((const void *)(__VA_ARGS__)) #endif OF_ASSUME_NONNULL_END + +#endif Index: src/exceptions/OFBindFailedException.h ================================================================== --- src/exceptions/OFBindFailedException.h +++ src/exceptions/OFBindFailedException.h @@ -18,10 +18,12 @@ #import "OFException.h" #ifndef OF_HAVE_SOCKETS # error No sockets available! #endif + +#import "socket.h" OF_ASSUME_NONNULL_BEGIN /*! * @class OFBindFailedException \ @@ -30,12 +32,15 @@ * @brief An exception indicating that binding a socket failed. */ @interface OFBindFailedException: OFException { id _socket; + /* IP */ OFString *_host; uint16_t _port; + /* IPX */ + uint8_t _packetType; int _errNo; } /*! * @brief The host on which binding failed. @@ -45,10 +50,15 @@ /*! * @brief The port on which binding failed. */ @property (readonly, nonatomic) uint16_t port; +/*! + * @brief The IPX packet type for which binding failed. + */ +@property (readonly, nonatomic) uint8_t packetType; + /*! * @brief The socket which could not be bound. */ @property (readonly, nonatomic) id socket; @@ -71,10 +81,24 @@ + (instancetype)exceptionWithHost: (OFString *)host port: (uint16_t)port socket: (id)socket errNo: (int)errNo; +/*! + * @brief Creates a new, autoreleased bind failed exception. + * + * @param port The IPX port to which binding failed + * @param packetType The IPX packet type for which binding failed + * @param socket The socket which could not be bound + * @param errNo The errno of the error that occurred + * @return A new, autoreleased bind failed exception + */ ++ (instancetype)exceptionWithPort: (uint16_t)port + packetType: (uint8_t)packetType + socket: (id)socket + errNo: (int)errNo; + - (instancetype)init OF_UNAVAILABLE; /*! * @brief Initializes an already allocated bind failed exception. * @@ -85,9 +109,23 @@ * @return An initialized bind failed exception */ - (instancetype)initWithHost: (OFString *)host port: (uint16_t)port socket: (id)socket - errNo: (int)errNo OF_DESIGNATED_INITIALIZER; + errNo: (int)errNo; + +/*! + * @brief Initializes an already allocated bind failed exception. + * + * @param port The IPX port to which binding failed + * @param packetType The IPX packet type for which binding failed + * @param socket The socket which could not be bound + * @param errNo The errno of the error that occurred + * @return An initialized bind failed exception + */ +- (instancetype)initWithPort: (uint16_t)port + packetType: (uint8_t)packetType + socket: (id)socket + errNo: (int)errNo; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFBindFailedException.m ================================================================== --- src/exceptions/OFBindFailedException.m +++ src/exceptions/OFBindFailedException.m @@ -19,25 +19,37 @@ #import "OFBindFailedException.h" #import "OFString.h" @implementation OFBindFailedException -@synthesize host = _host, port = _port, socket = _socket, errNo = _errNo; +@synthesize host = _host, port = _port, packetType = _packetType; +@synthesize socket = _socket, errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR } + (instancetype)exceptionWithHost: (OFString *)host port: (uint16_t)port - socket: (id)socket + socket: (id)sock errNo: (int)errNo { return [[[self alloc] initWithHost: host port: port - socket: socket + socket: sock + errNo: errNo] autorelease]; +} + ++ (instancetype)exceptionWithPort: (uint16_t)port + packetType: (uint8_t)packetType + socket: (id)sock + errNo: (int)errNo +{ + return [[[self alloc] initWithPort: port + packetType: packetType + socket: sock errNo: errNo] autorelease]; } - (instancetype)init { @@ -44,19 +56,39 @@ OF_INVALID_INIT_METHOD } - (instancetype)initWithHost: (OFString *)host port: (uint16_t)port - socket: (id)socket + socket: (id)sock errNo: (int)errNo { self = [super init]; @try { _host = [host copy]; _port = port; - _socket = [socket retain]; + _socket = [sock retain]; + _errNo = errNo; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithPort: (uint16_t)port + packetType: (uint8_t)packetType + socket: (id)sock + errNo: (int)errNo +{ + self = [super init]; + + @try { + _port = port; + _packetType = packetType; + _socket = [sock retain]; _errNo = errNo; } @catch (id e) { [self release]; @throw e; } @@ -72,11 +104,17 @@ [super dealloc]; } - (OFString *)description { - return [OFString stringWithFormat: - @"Binding to port %" @PRIu16 @" on host %@ failed in socket of " - @"type %@: %@", - _port, _host, [_socket class], of_strerror(_errNo)]; + if (_host != nil) + return [OFString stringWithFormat: + @"Binding to port %" @PRIu16 @" on host %@ failed in " + @"socket of type %@: %@", + _port, _host, [_socket class], of_strerror(_errNo)]; + else + return [OFString stringWithFormat: + @"Binding to port %" @PRIx16 @" for packet type %" @PRIx8 + @" failed in socket of type %@: %@", + _port, _packetType, [_socket class], of_strerror(_errNo)]; } @end Index: src/exceptions/OFConnectionFailedException.h ================================================================== --- src/exceptions/OFConnectionFailedException.h +++ src/exceptions/OFConnectionFailedException.h @@ -18,10 +18,12 @@ #import "OFException.h" #ifndef OF_HAVE_SOCKETS # error No sockets available! #endif + +#import "socket.h" OF_ASSUME_NONNULL_BEGIN /*! * @class OFConnectionFailedException \ @@ -32,10 +34,12 @@ @interface OFConnectionFailedException: OFException { id _socket; OFString *_host; uint16_t _port; + unsigned char _node[IPX_NODE_LEN]; + uint32_t _network; int _errNo; } /*! * @brief The socket which could not connect. @@ -50,10 +54,20 @@ /*! * @brief The port on the host to which the connection failed. */ @property (readonly, nonatomic) uint16_t port; +/*! + * @brief The IPX node to which the connection failed. + */ +@property (readonly, nonatomic) unsigned char *node; + +/*! + * @brief The IPX network of the node to which the connection failed. + */ +@property (readonly, nonatomic) uint32_t network; + /*! * @brief The errno of the error that occurred. */ @property (readonly, nonatomic) int errNo; @@ -71,10 +85,26 @@ + (instancetype)exceptionWithHost: (nullable OFString *)host port: (uint16_t)port socket: (id)socket errNo: (int)errNo; +/*! + * @brief Creates a new, autoreleased connection failed exception. + * + * @param node The node to which the connection failed + * @param network The IPX network of the node to which the connection failed + * @param port The port on the node to which the connection failed + * @param socket The socket which could not connect + * @param errNo The errno of the error that occurred + * @return A new, autoreleased connection failed exception + */ ++ (instancetype)exceptionWithNode: (unsigned char [_Nullable IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + socket: (id)socket + errNo: (int)errNo; + - (instancetype)init OF_UNAVAILABLE; /*! * @brief Initializes an already allocated connection failed exception. * @@ -85,9 +115,25 @@ * @return An initialized connection failed exception */ - (instancetype)initWithHost: (nullable OFString *)host port: (uint16_t)port socket: (id)socket - errNo: (int)errNo OF_DESIGNATED_INITIALIZER; + errNo: (int)errNo; + +/*! + * @brief Initializes an already allocated connection failed exception. + * + * @param node The node to which the connection failed + * @param network The IPX network of the node to which the connection failed + * @param port The port on the node to which the connection failed + * @param socket The socket which could not connect + * @param errNo The errno of the error that occurred + * @return An initialized connection failed exception + */ +- (instancetype)initWithNode: (unsigned char [_Nullable IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + socket: (id)socket + errNo: (int)errNo; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFConnectionFailedException.m ================================================================== --- src/exceptions/OFConnectionFailedException.m +++ src/exceptions/OFConnectionFailedException.m @@ -19,25 +19,39 @@ #import "OFConnectionFailedException.h" #import "OFString.h" @implementation OFConnectionFailedException -@synthesize host = _host, port = _port, socket = _socket, errNo = _errNo; +@synthesize host = _host, port = _port, network = _network, socket = _socket; +@synthesize errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR } + (instancetype)exceptionWithHost: (OFString *)host port: (uint16_t)port - socket: (id)socket + socket: (id)sock errNo: (int)errNo { return [[[self alloc] initWithHost: host port: port - socket: socket + socket: sock + errNo: errNo] autorelease]; +} + ++ (instancetype)exceptionWithNode: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + socket: (id)sock + errNo: (int)errNo +{ + return [[[self alloc] initWithNode: node + network: network + port: port + socket: sock errNo: errNo] autorelease]; } - (instancetype)init { @@ -44,19 +58,41 @@ OF_INVALID_INIT_METHOD } - (instancetype)initWithHost: (OFString *)host port: (uint16_t)port - socket: (id)socket + socket: (id)sock errNo: (int)errNo { self = [super init]; @try { _host = [host copy]; - _socket = [socket retain]; + _port = port; + _socket = [sock retain]; + _errNo = errNo; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (instancetype)initWithNode: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + socket: (id)sock + errNo: (int)errNo +{ + self = [super init]; + + @try { + memcpy(_node, node, IPX_NODE_LEN); + _network = network; _port = port; + _socket = [sock retain]; _errNo = errNo; } @catch (id e) { [self release]; @throw e; } @@ -69,20 +105,32 @@ [_host release]; [_socket release]; [super dealloc]; } + +- (unsigned char *)node +{ + return _node; +} - (OFString *)description { if (_host != nil) return [OFString stringWithFormat: @"A connection to %@ on port %" @PRIu16 @" could not be " @"established in socket of type %@: %@", _host, _port, [_socket class], of_strerror(_errNo)]; + else if (memcmp(_node, "\0\0\0\0\0", IPX_NODE_LEN) == 0) + return [OFString stringWithFormat: + @"A connection to %02X%02X%02X%02X%02X%02X port %" @PRIu16 + @" on network %" @PRIX32 " could not be established in " + @"socket of type %@: %@", + _node[0], _node[1], _node[2], _node[3], _node[4], _node[5], + _port, _network, [_socket class], of_strerror(_errNo)]; else return [OFString stringWithFormat: @"A connection could not be established in socket of " @"type %@: %@", [_socket class], of_strerror(_errNo)]; } @end Index: src/exceptions/OFException.m ================================================================== --- src/exceptions/OFException.m +++ src/exceptions/OFException.m @@ -25,13 +25,14 @@ #ifdef HAVE_DLFCN_H # include #endif #import "OFException.h" -#import "OFString.h" #import "OFArray.h" #import "OFLocale.h" +#import "OFString.h" +#import "OFSystemInfo.h" #import "OFInitializationFailedException.h" #import "OFLockFailedException.h" #import "OFUnlockFailedException.h" @@ -88,11 +89,11 @@ #endif if (errNo == 0) return @"Unknown error"; -#ifdef OF_WINDOWS +#if defined(OF_WINDOWS) && defined(OF_HAVE_SOCKETS) /* * These were translated from WSAE* errors to errno and thus Win32's * strerror_r() does not know about them. * * FIXME: These could have better descriptions! @@ -169,11 +170,11 @@ case EWOULDBLOCK: return @"EWOULDBLOCK"; } #endif -#if defined(HAVE_STRERROR_R) && defined(_GNU_SOURCE) +#if defined(STRERROR_R_RETURNS_CHARP) /* glibc uses a different strerror_r when _GNU_SOURCE is defined */ char *string; if ((string = strerror_r(errNo, buffer, 256)) == NULL) return @"Unknown error (strerror_r failed)"; @@ -209,23 +210,43 @@ #ifdef OF_WINDOWS OFString * of_windows_status_to_string(LSTATUS status) { + OFString *string = nil; void *buffer; - OFString *string; - - if (FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | - FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, status, 0, (LPWSTR)&buffer, 0, - NULL) != 0) { - @try { - string = [OFString stringWithUTF16String: buffer]; - } @finally { - LocalFree(buffer); - } - } else + + if ([OFSystemInfo isWindowsNT]) { + if (FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, status, 0, + (LPWSTR)&buffer, 0, NULL) != 0) { + @try { + string = [OFString + stringWithUTF16String: buffer]; + } @finally { + LocalFree(buffer); + } + } + } else { + if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, status, 0, + (LPSTR)&buffer, 0, NULL) != 0) { + @try { + string = [OFString + stringWithCString: buffer + encoding: [OFLocale encoding]]; + } @finally { + LocalFree(buffer); + } + } + } + + if (string == nil) string = [OFString stringWithFormat: @"Status code %u", status]; return string; } #endif Index: src/exceptions/OFGetOptionFailedException.h ================================================================== --- src/exceptions/OFGetOptionFailedException.h +++ src/exceptions/OFGetOptionFailedException.h @@ -17,28 +17,26 @@ #import "OFException.h" OF_ASSUME_NONNULL_BEGIN -@class OFStream; - /*! * @class OFGetOptionFailedException \ * OFGetOptionFailedException.h ObjFW/OFGetOptionFailedException.h * - * @brief An exception indicating that getting an option for a stream failed. + * @brief An exception indicating that getting an option for an object failed. */ @interface OFGetOptionFailedException: OFException { - OFStream *_stream; + id _object; int _errNo; } /*! - * @brief The stream for which the option could not be retrieved. + * @brief The object for which the option could not be retrieved. */ -@property (readonly, nonatomic) OFStream *stream; +@property (readonly, nonatomic) id object; /*! * @brief The errno of the error that occurred. */ @property (readonly, nonatomic) int errNo; @@ -46,26 +44,26 @@ + (instancetype)exception OF_UNAVAILABLE; /*! * @brief Creates a new, autoreleased get option failed exception. * - * @param stream The stream for which the option could not be gotten + * @param object The object for which the option could not be retrieved * @param errNo The errno of the error that occurred * @return A new, autoreleased get option failed exception */ -+ (instancetype)exceptionWithStream: (OFStream *)stream ++ (instancetype)exceptionWithObject: (id)object errNo: (int)errNo; - (instancetype)init OF_UNAVAILABLE; /*! * @brief Initializes an already allocated get option failed exception. * - * @param stream The stream for which the option could not be gotten + * @param object The object for which the option could not be retrieved * @param errNo The errno of the error that occurred * @return An initialized get option failed exception */ -- (instancetype)initWithStream: (OFStream *)stream +- (instancetype)initWithObject: (id)object errNo: (int)errNo OF_DESIGNATED_INITIALIZER; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFGetOptionFailedException.m ================================================================== --- src/exceptions/OFGetOptionFailedException.m +++ src/exceptions/OFGetOptionFailedException.m @@ -17,52 +17,51 @@ #include "config.h" #import "OFGetOptionFailedException.h" #import "OFString.h" -#import "OFStream.h" @implementation OFGetOptionFailedException -@synthesize stream = _stream, errNo = _errNo; +@synthesize object = _object, errNo = _errNo; + (instancetype)exception { OF_UNRECOGNIZED_SELECTOR } -+ (instancetype)exceptionWithStream: (OFStream *)stream ++ (instancetype)exceptionWithObject: (id)object errNo: (int)errNo { - return [[[self alloc] initWithStream: stream + return [[[self alloc] initWithObject: object errNo: errNo] autorelease]; } - (instancetype)init { OF_INVALID_INIT_METHOD } -- (instancetype)initWithStream: (OFStream *)stream +- (instancetype)initWithObject: (id)object errNo: (int)errNo { self = [super init]; - _stream = [stream retain]; + _object = [object retain]; _errNo = errNo; return self; } - (void)dealloc { - [_stream release]; + [_object release]; [super dealloc]; } - (OFString *)description { return [OFString stringWithFormat: - @"Getting an option in a stream of type %@ failed: %@", - _stream.class, of_strerror(_errNo)]; + @"Getting an option in an object of type %@ failed: %@", + [_object class], of_strerror(_errNo)]; } @end Index: src/exceptions/OFGetWindowsRegistryValueFailedException.h ================================================================== --- src/exceptions/OFGetWindowsRegistryValueFailedException.h +++ src/exceptions/OFGetWindowsRegistryValueFailedException.h @@ -30,11 +30,11 @@ * @brief An exception indicating that getting a Windows registry value failed. */ @interface OFGetWindowsRegistryValueFailedException: OFException { OFWindowsRegistryKey *_registryKey; - OFString *_Nullable _value, *_Nullable _subkeyPath; + OFString *_Nullable _value; DWORD _flags; LSTATUS _status; } /*! @@ -45,20 +45,10 @@ /*! * @brief The value which could not be retrieved. */ @property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *value; -/*! - * @brief The subkey path at which getting the value failed. - */ -@property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *subkeyPath; - -/*! - * @brief The flags with which getting the value failed. - */ -@property (readonly, nonatomic) DWORD flags; - /*! * @brief The status returned by RegGetValueEx(). */ @property (readonly, nonatomic) LSTATUS status; @@ -67,19 +57,15 @@ * exception. * * @param registryKey The registry key on which getting the value at the sub * key path failed * @param value The value which could not be retrieved - * @param subkeyPath The subkey path at which getting the value failed - * @param flags The flags with which getting the value failed * @param status The status returned by RegGetValueEx() * @return A new, autoreleased get Windows registry value failed exception */ + (instancetype)exceptionWithRegistryKey: (OFWindowsRegistryKey *)registryKey value: (nullable OFString *)value - subkeyPath: (nullable OFString *)subkeyPath - flags: (DWORD)flags status: (LSTATUS)status; - (instancetype)init OF_UNAVAILABLE; /*! @@ -87,18 +73,14 @@ * exception. * * @param registryKey The registry key on which getting the value at the sub * key path failed * @param value The value which could not be retrieved - * @param subkeyPath The subkey path at which getting the value failed - * @param flags The flags with which getting the value failed * @param status The status returned by RegGetValueEx() * @return An initialized get Windows registry value failed exception */ - (instancetype)initWithRegistryKey: (OFWindowsRegistryKey *)registryKey value: (nullable OFString *)value - subkeyPath: (nullable OFString *)subkeyPath - flags: (DWORD)flags status: (LSTATUS)status OF_DESIGNATED_INITIALIZER; @end OF_ASSUME_NONNULL_END Index: src/exceptions/OFGetWindowsRegistryValueFailedException.m ================================================================== --- src/exceptions/OFGetWindowsRegistryValueFailedException.m +++ src/exceptions/OFGetWindowsRegistryValueFailedException.m @@ -18,23 +18,18 @@ #include "config.h" #import "OFGetWindowsRegistryValueFailedException.h" @implementation OFGetWindowsRegistryValueFailedException -@synthesize registryKey = _registryKey, value = _value; -@synthesize subkeyPath = _subkeyPath, flags = _flags, status = _status; +@synthesize registryKey = _registryKey, value = _value, status = _status; + (instancetype)exceptionWithRegistryKey: (OFWindowsRegistryKey *)registryKey value: (OFString *)value - subkeyPath: (OFString *)subkeyPath - flags: (DWORD)flags status: (LSTATUS)status { return [[[self alloc] initWithRegistryKey: registryKey value: value - subkeyPath: subkeyPath - flags: flags status: status] autorelease]; } - (instancetype)init { @@ -41,21 +36,17 @@ OF_INVALID_INIT_METHOD } - (instancetype)initWithRegistryKey: (OFWindowsRegistryKey *)registryKey value: (OFString *)value - subkeyPath: (OFString *)subkeyPath - flags: (DWORD)flags status: (LSTATUS)status { self = [super init]; @try { _registryKey = [registryKey retain]; _value = [value copy]; - _subkeyPath = [subkeyPath copy]; - _flags = flags; _status = status; } @catch (id e) { [self release]; @throw e; } @@ -65,17 +56,16 @@ - (void)dealloc { [_registryKey release]; [_value release]; - [_subkeyPath release]; [super dealloc]; } - (OFString *)description { return [OFString stringWithFormat: - @"Failed to get value %@ at subkey path %@: %@", - _value, _subkeyPath, of_windows_status_to_string(_status)]; + @"Failed to get value %@: %@", + _value, of_windows_status_to_string(_status)]; } @end Index: src/macros.h ================================================================== --- src/macros.h +++ src/macros.h @@ -12,10 +12,13 @@ * Alternatively, it may be distributed under the terms of the GNU General * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ + +#ifndef OBJFW_MACROS_H +#define OBJFW_MACROS_H #include "objfw-defs.h" #ifndef __STDC_LIMIT_MACROS # define __STDC_LIMIT_MACROS @@ -35,19 +38,19 @@ #include "platform.h" #ifdef OF_OBJFW_RUNTIME # ifdef OF_COMPILING_OBJFW -# import "ObjFWRT.h" +# include "ObjFWRT.h" # else -# import +# include # endif #endif #ifdef OF_APPLE_RUNTIME -# import -# import -# import +# include +# include +# include #endif #if defined(__GNUC__) # define restrict __restrict__ #elif __STDC_VERSION__ < 199901L @@ -547,37 +550,53 @@ #else # define OF_BSWAP16(i) OF_BSWAP16_CONST(i) # define OF_BSWAP32(i) OF_BSWAP32_CONST(i) # define OF_BSWAP64(i) OF_BSWAP64_CONST(i) #endif + +static OF_INLINE uint32_t +OF_FLOAT_TO_INT_RAW(float f) +{ + uint32_t ret; + memcpy(&ret, &f, 4); + return ret; +} + +static OF_INLINE float +OF_INT_TO_FLOAT_RAW(uint32_t uInt32) +{ + float ret; + memcpy(&ret, &uInt32, 4); + return ret; +} + +static OF_INLINE uint64_t +OF_DOUBLE_TO_INT_RAW(double d) +{ + uint64_t ret; + memcpy(&ret, &d, 8); + return ret; +} + +static OF_INLINE double +OF_INT_TO_DOUBLE_RAW(uint64_t uInt64) +{ + double ret; + memcpy(&ret, &uInt64, 8); + return ret; +} static OF_INLINE float OF_CONST_FUNC OF_BSWAP_FLOAT(float f) { - union { - float f; - uint32_t i; - } u; - - u.f = f; - u.i = OF_BSWAP32(u.i); - - return u.f; + return OF_INT_TO_FLOAT_RAW(OF_BSWAP32(OF_FLOAT_TO_INT_RAW(f))); } static OF_INLINE double OF_CONST_FUNC OF_BSWAP_DOUBLE(double d) { - union { - double d; - uint64_t i; - } u; - - u.d = d; - u.i = OF_BSWAP64(u.i); - - return u.d; + return OF_INT_TO_DOUBLE_RAW(OF_BSWAP64(OF_DOUBLE_TO_INT_RAW(d))); } #ifdef OF_BIG_ENDIAN # define OF_BSWAP16_IF_BE(i) OF_BSWAP16(i) # define OF_BSWAP32_IF_BE(i) OF_BSWAP32(i) @@ -886,5 +905,7 @@ gettimeofday(&tv, NULL); srand((unsigned)(tv.tv_sec ^ tv.tv_usec)); return (((uint32_t)(rand()) << 16) | ((uint32_t)(rand()) & 0xFFFF)); #endif } + +#endif Index: src/objfw-defs.h.in ================================================================== --- src/objfw-defs.h.in +++ src/objfw-defs.h.in @@ -13,14 +13,18 @@ #undef OF_HAVE_CHMOD #undef OF_HAVE_CHOWN #undef OF_HAVE_FILES #undef OF_HAVE_FORWARDING_TARGET_FOR_SELECTOR #undef OF_HAVE_IPV6 +#undef OF_HAVE_IPX #undef OF_HAVE_LIMITS_H #undef OF_HAVE_LINK #undef OF_HAVE_MAX_ALIGN_T #undef OF_HAVE_NETINET_IN_H +#undef OF_HAVE_NETINET_SCTP_H +#undef OF_HAVE_NETINET_TCP_H +#undef OF_HAVE_NETIPX_IPX_H #undef OF_HAVE_OSATOMIC #undef OF_HAVE_OSATOMIC_64 #undef OF_HAVE_PIPE #undef OF_HAVE_PLEDGE #undef OF_HAVE_PLUGINS Index: src/of_asprintf.m ================================================================== --- src/of_asprintf.m +++ src/of_asprintf.m @@ -97,21 +97,47 @@ #ifndef HAVE_ASPRINTF static int vasprintf(char **string, const char *format, va_list arguments) { - int length; + int expectedLength, length; va_list argumentsCopy; va_copy(argumentsCopy, arguments); - if ((length = vsnprintf(NULL, 0, format, argumentsCopy)) < 0) - return length; - if ((*string = malloc((size_t)length + 1)) == NULL) + expectedLength = vsnprintf(NULL, 0, format, argumentsCopy); + if (expectedLength == -1) + /* + * We have no way to know how large it is. Let's try 64 KB and + * hope. + */ + expectedLength = 65535; + + if ((*string = malloc((size_t)expectedLength + 1)) == NULL) + return -1; + + length = vsnprintf(*string, (size_t)expectedLength + 1, + format, arguments); + + if (length == -1 || length > expectedLength) { + free(*string); + *string = NULL; return -1; + } - return vsnprintf(*string, (size_t)length + 1, format, arguments); + /* + * In case we could not determine the size, resize to the actual size + * needed, but ignore any failure to do so. + */ + if (length < expectedLength) { + char *resized; + + if ((resized = realloc(*string, length + 1)) != NULL) + *string = resized; + } + + return length; } static int asprintf(char **string, const char *format, ...) { @@ -278,12 +304,13 @@ ctx->lengthModifier = LENGTH_MODIFIER_J; break; case 'z': #if defined(OF_WINDOWS) - if (!appendSubformat(ctx, "I", 1)) - return false; + if (sizeof(size_t) == 8) + if (!appendSubformat(ctx, "I64", 3)) + return false; #elif defined(_NEWLIB_VERSION) if (!appendSubformat(ctx, "l", 1)) return false; #else if (!appendSubformat(ctx, ctx->format + ctx->i, 1)) @@ -293,12 +320,13 @@ ctx->lengthModifier = LENGTH_MODIFIER_Z; break; case 't': #if defined(OF_WINDOWS) - if (!appendSubformat(ctx, "I", 1)) - return false; + if (sizeof(ptrdiff_t) == 8) + if (!appendSubformat(ctx, "I64", 3)) + return false; #elif defined(_NEWLIB_VERSION) if (!appendSubformat(ctx, "l", 1)) return false; #else if (!appendSubformat(ctx, ctx->format + ctx->i, 1)) @@ -352,10 +380,13 @@ static bool formatConversionSpecifierState(struct context *ctx) { char *tmp = NULL; int tmpLen = 0; +#ifndef HAVE_ASPRINTF_L + OFString *point; +#endif if (!appendSubformat(ctx, ctx->format + ctx->i, 1)) return false; switch (ctx->format[ctx->i]) { @@ -546,30 +577,34 @@ default: return false; } #ifndef HAVE_ASPRINTF_L + if (tmpLen == -1) + return false; + /* * If there's no asprintf_l, we have no other choice than to * use this ugly hack to replace the locale's decimal point * back to ".". */ - if (!ctx->useLocale) { + point = [OFLocale decimalPoint]; + + if (!ctx->useLocale && point != nil && ![point isEqual: @"."]) { void *pool = objc_autoreleasePoolPush(); char *tmp2; @try { OFMutableString *tmpStr = [OFMutableString stringWithUTF8String: tmp length: tmpLen]; - OFString *point = [OFLocale decimalPoint]; - if (point != nil) - [tmpStr - replaceOccurrencesOfString: point - withString: @"."]; + [tmpStr replaceOccurrencesOfString: point + withString: @"."]; + if (tmpStr.UTF8StringLength > INT_MAX) return false; + tmpLen = (int)tmpStr.UTF8StringLength; tmp2 = malloc(tmpLen); memcpy(tmp2, tmpStr.UTF8String, tmpLen); } @finally { free(tmp); Index: src/platform.h ================================================================== --- src/platform.h +++ src/platform.h @@ -88,15 +88,18 @@ # define OF_M68020 # endif # if defined(__mc68010__) || defined(OF_M68020) # define OF_M68010 # endif -# if defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 64 -# define OF_RISC_V_64 -# elif defined(__riscv) -# define OF_RISC_V -# endif +#elif defined(__riscv) && defined(__riscv_xlen) && __riscv_xlen == 64 +# define OF_RISC_V_64 +#elif defined(__riscv) +# define OF_RISC_V +#elif defined(__s390x__) +# define OF_S390X +#elif defined(__s390__) +# define OF_S390 #endif #if defined(__APPLE__) # include # if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) || \ Index: src/platform/posix/OFProcess.m ================================================================== --- src/platform/posix/OFProcess.m +++ src/platform/posix/OFProcess.m @@ -129,10 +129,13 @@ @try { void *pool = objc_autoreleasePoolPush(); const char *path; char **argv; + _pid = -1; + _readPipe[0] = _writePipe[1] = -1; + if (pipe(_readPipe) != 0 || pipe(_writePipe) != 0) @throw [OFInitializationFailedException exceptionWithClass: self.class]; path = [program cStringWithEncoding: [OFLocale encoding]]; @@ -372,6 +375,19 @@ _pid = -1; _readPipe[0] = -1; [super close]; } + +- (int)waitForTermination +{ + if (_readPipe[0] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_pid != -1) { + waitpid(_pid, &_status, 0); + _pid = -1; + } + + return WEXITSTATUS(_status); +} @end Index: src/platform/posix/thread.m ================================================================== --- src/platform/posix/thread.m +++ src/platform/posix/thread.m @@ -58,18 +58,18 @@ maxPrio = sched_get_priority_max(policy); if (minPrio == -1 || maxPrio == -1) minPrio = maxPrio = 0; } +#endif if (pthread_attr_getschedparam(&pattr, ¶m) != 0) normalPrio = param.sched_priority; else minPrio = maxPrio = 0; pthread_attr_destroy(&pattr); -#endif } } static void * functionWrapper(void *data) Index: src/platform/windows/OFProcess.m ================================================================== --- src/platform/windows/OFProcess.m +++ src/platform/windows/OFProcess.m @@ -19,14 +19,16 @@ #include #include #import "OFProcess.h" -#import "OFString.h" #import "OFArray.h" +#import "OFData.h" #import "OFDictionary.h" -#import "OFData.h" +#import "OFLocale.h" +#import "OFString.h" +#import "OFSystemInfo.h" #import "OFInitializationFailedException.h" #import "OFNotOpenException.h" #import "OFOutOfRangeException.h" #import "OFReadFailedException.h" @@ -33,11 +35,12 @@ #import "OFWriteFailedException.h" #include @interface OFProcess () -- (of_char16_t *)of_environmentForDictionary: (OFDictionary *)dictionary; +- (of_char16_t *)of_wideEnvironmentForDictionary: (OFDictionary *)dictionary; +- (char *)of_environmentForDictionary: (OFDictionary *)environment; @end @implementation OFProcess + (instancetype)processWithProgram: (OFString *)program { @@ -111,15 +114,15 @@ self = [super init]; @try { SECURITY_ATTRIBUTES sa; PROCESS_INFORMATION pi; - STARTUPINFOW si; void *pool; OFMutableString *argumentsString; - of_char16_t *argumentsCopy; - size_t length; + + _process = INVALID_HANDLE_VALUE; + _readPipe[0] = _writePipe[1] = NULL; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; @@ -126,30 +129,25 @@ if (!CreatePipe(&_readPipe[0], &_readPipe[1], &sa, 0)) @throw [OFInitializationFailedException exceptionWithClass: self.class]; if (!SetHandleInformation(_readPipe[0], HANDLE_FLAG_INHERIT, 0)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; + if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; if (!CreatePipe(&_writePipe[0], &_writePipe[1], &sa, 0)) @throw [OFInitializationFailedException exceptionWithClass: self.class]; if (!SetHandleInformation(_writePipe[1], HANDLE_FLAG_INHERIT, 0)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; + if (GetLastError() != ERROR_CALL_NOT_IMPLEMENTED) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; memset(&pi, 0, sizeof(pi)); - memset(&si, 0, sizeof(si)); - - si.cb = sizeof(si); - si.hStdInput = _writePipe[0]; - si.hStdOutput = _readPipe[1]; - si.hStdError = GetStdHandle(STD_ERROR_HANDLE); - si.dwFlags |= STARTF_USESTDHANDLES; pool = objc_autoreleasePoolPush(); argumentsString = [OFMutableString stringWithString: programName]; @@ -182,25 +180,57 @@ if (containsSpaces) [argumentsString appendString: @"\""]; } - length = argumentsString.UTF16StringLength; - argumentsCopy = [self allocMemoryWithSize: sizeof(of_char16_t) - count: length + 1]; - memcpy(argumentsCopy, argumentsString.UTF16String, - (argumentsString.UTF16StringLength + 1) * 2); - @try { - if (!CreateProcessW(program.UTF16String, - argumentsCopy, NULL, NULL, TRUE, - CREATE_UNICODE_ENVIRONMENT, + if ([OFSystemInfo isWindowsNT]) { + size_t length; + of_char16_t *argumentsCopy; + STARTUPINFOW si; + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.hStdInput = _writePipe[0]; + si.hStdOutput = _readPipe[1]; + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + si.dwFlags |= STARTF_USESTDHANDLES; + + length = argumentsString.UTF16StringLength; + argumentsCopy = [self + allocMemoryWithSize: sizeof(of_char16_t) + count: length + 1]; + memcpy(argumentsCopy, argumentsString.UTF16String, + (length + 1) * 2); + @try { + if (!CreateProcessW(program.UTF16String, + argumentsCopy, NULL, NULL, TRUE, + CREATE_UNICODE_ENVIRONMENT, + [self of_wideEnvironmentForDictionary: + environment], NULL, &si, &pi)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + } @finally { + [self freeMemory: argumentsCopy]; + } + } else { + of_string_encoding_t encoding = [OFLocale encoding]; + STARTUPINFO si; + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.hStdInput = _writePipe[0]; + si.hStdOutput = _readPipe[1]; + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + si.dwFlags |= STARTF_USESTDHANDLES; + + if (!CreateProcessA([program cStringWithEncoding: + encoding], (char *)[argumentsString + cStringWithEncoding: encoding], NULL, NULL, TRUE, 0, [self of_environmentForDictionary: environment], NULL, &si, &pi)) @throw [OFInitializationFailedException exceptionWithClass: self.class]; - } @finally { - [self freeMemory: argumentsCopy]; } objc_autoreleasePoolPop(pool); _process = pi.hProcess; @@ -222,11 +252,11 @@ [self close]; [super dealloc]; } -- (of_char16_t *)of_environmentForDictionary: (OFDictionary *)environment +- (of_char16_t *)of_wideEnvironmentForDictionary: (OFDictionary *)environment { OFMutableData *env; OFEnumerator *keyEnumerator, *objectEnumerator; OFString *key, *object; const of_char16_t equal = '='; @@ -250,10 +280,41 @@ [env addItems: &zero count: 1]; } [env addItems: zero count: 2]; + + return env.mutableItems; +} + +- (char *)of_environmentForDictionary: (OFDictionary *)environment +{ + of_string_encoding_t encoding = [OFLocale encoding]; + OFMutableData *env; + OFEnumerator *keyEnumerator, *objectEnumerator; + OFString *key, *object; + + if (environment == nil) + return NULL; + + env = [OFMutableData data]; + + keyEnumerator = [environment keyEnumerator]; + objectEnumerator = [environment objectEnumerator]; + while ((key = [keyEnumerator nextObject]) != nil && + (object = [objectEnumerator nextObject]) != nil) { + [env addItems: [key cStringWithEncoding: encoding] + count: [key cStringLengthWithEncoding: encoding]]; + [env addItems: "=" + count: 1]; + [env addItems: [object cStringWithEncoding: encoding] + count: [object cStringLengthWithEncoding: encoding]]; + [env addItems: "" + count: 1]; + } + [env addItems: "\0" + count: 2]; return env.mutableItems; } - (bool)lowlevelIsAtEndOfStream @@ -343,6 +404,28 @@ _process = INVALID_HANDLE_VALUE; _readPipe[0] = NULL; [super close]; } + +- (int)waitForTermination +{ + if (_readPipe[0] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_process != INVALID_HANDLE_VALUE) { + DWORD exitCode; + + WaitForSingleObject(_process, INFINITE); + + if (GetExitCodeProcess(_process, &exitCode)) + _status = exitCode; + else + _status = GetLastError(); + + CloseHandle(_process); + _process = INVALID_HANDLE_VALUE; + } + + return _status; +} @end Index: src/runtime/linklib/linklib.m ================================================================== --- src/runtime/linklib/linklib.m +++ src/runtime/linklib/linklib.m @@ -61,11 +61,11 @@ extern void *__deregister_frame_info(const void *); struct Library *ObjFWRTBase; void *__objc_class_name_Protocol; -static void +static void __attribute__((__used__)) ctor(void) { static bool initialized = false; struct objc_libc libc = { .malloc = malloc, @@ -114,11 +114,11 @@ } initialized = true; } -static void __attribute__((__unused__)) +static void __attribute__((__used__)) dtor(void) { CloseLibrary(ObjFWRTBase); } Index: src/socket.h ================================================================== --- src/socket.h +++ src/socket.h @@ -32,16 +32,25 @@ # include #endif #ifdef OF_HAVE_NETINET_TCP_H # include #endif +#ifdef OF_HAVE_NETINET_SCTP_H +# include +#endif +#ifdef OF_HAVE_NETIPX_IPX_H +# include +#endif #include "platform.h" #ifdef OF_WINDOWS # include # include +# ifdef OF_HAVE_IPX +# include +# endif #endif /*! @file */ #ifdef OF_WII @@ -87,10 +96,12 @@ OF_SOCKET_ADDRESS_FAMILY_UNKNOWN, /** IPv4 */ OF_SOCKET_ADDRESS_FAMILY_IPV4, /** IPv6 */ OF_SOCKET_ADDRESS_FAMILY_IPV6, + /** IPX */ + OF_SOCKET_ADDRESS_FAMILY_IPX, /** Any address family */ OF_SOCKET_ADDRESS_FAMILY_ANY = 255 } of_socket_address_family_t; #ifndef OF_HAVE_IPV6 @@ -103,16 +114,34 @@ } sin6_addr; uint32_t sin6_scope_id; }; #endif +#ifndef OF_HAVE_IPX +# define IPX_NODE_LEN 6 +struct sockaddr_ipx { + sa_family_t sipx_family; + uint32_t sipx_network; + unsigned char sipx_node[IPX_NODE_LEN]; + uint16_t sipx_port; + uint8_t sipx_type; +}; +#endif +#ifdef OF_WINDOWS +# define IPX_NODE_LEN 6 +# define sipx_family sa_family +# define sipx_network sa_netnum +# define sipx_node sa_nodenum +# define sipx_port sa_socket +#endif + /*! * @struct of_socket_address_t socket.h ObjFW/socket.h * * @brief A struct which represents a host / port pair for a socket. */ -typedef struct OF_BOXABLE { +struct OF_BOXABLE of_socket_address_t { /* * Even though struct sockaddr contains the family, we need to use our * own family, as we need to support storing an IPv6 address on systems * that don't support IPv6. These may not have AF_INET6 defined and we * can't just define it, as the value is system-dependent and might @@ -121,13 +150,15 @@ of_socket_address_family_t family; union { struct sockaddr sockaddr; struct sockaddr_in in; struct sockaddr_in6 in6; + struct sockaddr_ipx ipx; } sockaddr; socklen_t length; -} of_socket_address_t; +}; +typedef struct of_socket_address_t of_socket_address_t; #ifdef __cplusplus extern "C" { #endif /*! @@ -148,21 +179,30 @@ * @return The parsed IPv4 and port as an of_socket_address_t */ extern of_socket_address_t of_socket_address_parse_ipv4( OFString *IP, uint16_t port); -#ifdef OF_HAVE_IPV6 /*! * @brief Parses the specified IPv6 and port into an of_socket_address_t. * * @param IP The IPv6 to parse * @param port The port to use * @return The parsed IPv6 and port as an of_socket_address_t */ extern of_socket_address_t of_socket_address_parse_ipv6( OFString *IP, uint16_t port); -#endif + +/*! + * @brief Creates an IPX address for the specified network, node and port. + * + * @param node The node in the IPX network + * @param network The IPX network + * @param port The IPX port (sometimes called socket number) on the node + */ +extern of_socket_address_t of_socket_address_ipx( + const unsigned char node[_Nonnull IPX_NODE_LEN], uint32_t network, + uint16_t port); /*! * @brief Compares two of_socket_address_t for equality. * * @param address1 The address to compare with the second address @@ -211,10 +251,48 @@ * @return The port of the address */ extern uint16_t of_socket_address_get_port( const of_socket_address_t *_Nonnull address); +/*! + * @brief Sets the IPX network of the specified of_socket_address_t. + * + * @param address The address on which to set the IPX network + * @param network The IPX network to set on the address + */ +extern void of_socket_address_set_ipx_network( + of_socket_address_t *_Nonnull address, uint32_t network); + +/*! + * @brief Returns the IPX network of the specified of_socket_address_t. + * + * @param address The address on which to get the IPX network + * @return The IPX network of the address + */ +extern uint32_t of_socket_address_get_ipx_network( + const of_socket_address_t *_Nonnull address); + +/*! + * @brief Sets the IPX node of the specified of_socket_address_t. + * + * @param address The address on which to set the IPX node + * @param node The IPX node to set on the address + */ +extern void of_socket_address_set_ipx_node( + of_socket_address_t *_Nonnull address, + const unsigned char node[_Nonnull IPX_NODE_LEN]); + +/*! + * @brief Gets the IPX node of the specified of_socket_address_t. + * + * @param address The address on which to get the IPX node + * @param node A byte array to store the IPX node of the address + */ +extern void of_socket_address_get_ipx_node( + const of_socket_address_t *_Nonnull address, + unsigned char node[_Nonnull IPX_NODE_LEN]); + extern bool of_socket_init(void); #if defined(OF_HAVE_THREADS) && defined(OF_AMIGAOS) extern void of_socket_deinit(void); #endif extern int of_socket_errno(void); Index: src/socket.m ================================================================== --- src/socket.m +++ src/socket.m @@ -344,12 +344,10 @@ #endif of_socket_address_t of_socket_address_parse_ipv4(OFString *IPv4, uint16_t port) { - /* TODO: Support IPs that are not in the a.b.c.d format? */ - void *pool = objc_autoreleasePoolPush(); OFCharacterSet *whitespaceCharacterSet = [OFCharacterSet whitespaceCharacterSet]; of_socket_address_t ret; struct sockaddr_in *addrIn = &ret.sockaddr.in; @@ -507,17 +505,42 @@ return of_socket_address_parse_ipv6(IP, port); } @catch (OFInvalidFormatException *e) { return of_socket_address_parse_ipv4(IP, port); } } + +of_socket_address_t +of_socket_address_ipx(const unsigned char node[IPX_NODE_LEN], uint32_t network, + uint16_t port) +{ + of_socket_address_t ret; + + memset(&ret, '\0', sizeof(ret)); + ret.family = OF_SOCKET_ADDRESS_FAMILY_IPX; + ret.length = sizeof(ret.sockaddr.ipx); + +#ifdef AF_IPX + ret.sockaddr.ipx.sipx_family = AF_IPX; +#else + ret.sockaddr.ipx.sipx_family = AF_UNSPEC; +#endif + memcpy(ret.sockaddr.ipx.sipx_node, node, IPX_NODE_LEN); + network = OF_BSWAP32_IF_LE(network); + memcpy(&ret.sockaddr.ipx.sipx_network, &network, + sizeof(ret.sockaddr.ipx.sipx_network)); + ret.sockaddr.ipx.sipx_port = OF_BSWAP16_IF_LE(port); + + return ret; +} bool of_socket_address_equal(const of_socket_address_t *address1, const of_socket_address_t *address2) { const struct sockaddr_in *addrIn1, *addrIn2; const struct sockaddr_in6 *addrIn6_1, *addrIn6_2; + const struct sockaddr_ipx *addrIPX1, *addrIPX2; if (address1->family != address2->family) return false; switch (address1->family) { @@ -553,10 +576,28 @@ if (memcmp(addrIn6_1->sin6_addr.s6_addr, addrIn6_2->sin6_addr.s6_addr, sizeof(addrIn6_1->sin6_addr.s6_addr)) != 0) return false; + break; + case OF_SOCKET_ADDRESS_FAMILY_IPX: + if (address1->length < (socklen_t)sizeof(struct sockaddr_ipx) || + address2->length < (socklen_t)sizeof(struct sockaddr_ipx)) + @throw [OFInvalidArgumentException exception]; + + addrIPX1 = &address1->sockaddr.ipx; + addrIPX2 = &address2->sockaddr.ipx; + + if (addrIPX1->sipx_port != addrIPX2->sipx_port) + return false; + if (memcmp(&addrIPX1->sipx_network, &addrIPX2->sipx_network, + 4) != 0) + return false; + if (memcmp(addrIPX1->sipx_node, addrIPX2->sipx_node, + IPX_NODE_LEN) != 0) + return false; + break; default: @throw [OFInvalidArgumentException exception]; } @@ -599,10 +640,30 @@ for (size_t i = 0; i < sizeof(address->sockaddr.in6.sin6_addr.s6_addr); i++) OF_HASH_ADD(hash, address->sockaddr.in6.sin6_addr.s6_addr[i]); + break; + case OF_SOCKET_ADDRESS_FAMILY_IPX:; + unsigned char network[ + sizeof(address->sockaddr.ipx.sipx_network)]; + + if (address->length < (socklen_t)sizeof(struct sockaddr_ipx)) + @throw [OFInvalidArgumentException exception]; + + OF_HASH_ADD(hash, address->sockaddr.ipx.sipx_port >> 8); + OF_HASH_ADD(hash, address->sockaddr.ipx.sipx_port); + + memcpy(network, &address->sockaddr.ipx.sipx_network, + sizeof(network)); + + for (size_t i = 0; i < sizeof(network); i++) + OF_HASH_ADD(hash, network[i]); + + for (size_t i = 0; i < IPX_NODE_LEN; i++) + OF_HASH_ADD(hash, address->sockaddr.ipx.sipx_node[i]); + break; default: @throw [OFInvalidArgumentException exception]; } @@ -719,10 +780,13 @@ address->sockaddr.in.sin_port = OF_BSWAP16_IF_LE(port); break; case OF_SOCKET_ADDRESS_FAMILY_IPV6: address->sockaddr.in6.sin6_port = OF_BSWAP16_IF_LE(port); break; + case OF_SOCKET_ADDRESS_FAMILY_IPX: + address->sockaddr.ipx.sipx_port = OF_BSWAP16_IF_LE(port); + break; default: @throw [OFInvalidArgumentException exception]; } } @@ -732,9 +796,56 @@ switch (address->family) { case OF_SOCKET_ADDRESS_FAMILY_IPV4: return OF_BSWAP16_IF_LE(address->sockaddr.in.sin_port); case OF_SOCKET_ADDRESS_FAMILY_IPV6: return OF_BSWAP16_IF_LE(address->sockaddr.in6.sin6_port); + case OF_SOCKET_ADDRESS_FAMILY_IPX: + return OF_BSWAP16_IF_LE(address->sockaddr.ipx.sipx_port); default: @throw [OFInvalidArgumentException exception]; } } + +void +of_socket_address_set_ipx_network(of_socket_address_t *address, + uint32_t network) +{ + if (address->family != OF_SOCKET_ADDRESS_FAMILY_IPX) + @throw [OFInvalidArgumentException exception]; + + network = OF_BSWAP32_IF_LE(network); + memcpy(&address->sockaddr.ipx.sipx_network, &network, + sizeof(address->sockaddr.ipx.sipx_network)); +} + +uint32_t +of_socket_address_get_ipx_network(const of_socket_address_t *address) +{ + uint32_t network; + + if (address->family != OF_SOCKET_ADDRESS_FAMILY_IPX) + @throw [OFInvalidArgumentException exception]; + + memcpy(&network, &address->sockaddr.ipx.sipx_network, sizeof(network)); + + return OF_BSWAP32_IF_LE(network); +} + +void +of_socket_address_set_ipx_node(of_socket_address_t *address, + const unsigned char node[IPX_NODE_LEN]) +{ + if (address->family != OF_SOCKET_ADDRESS_FAMILY_IPX) + @throw [OFInvalidArgumentException exception]; + + memcpy(address->sockaddr.ipx.sipx_node, node, IPX_NODE_LEN); +} + +void +of_socket_address_get_ipx_node(const of_socket_address_t *address, + unsigned char node[IPX_NODE_LEN]) +{ + if (address->family != OF_SOCKET_ADDRESS_FAMILY_IPX) + @throw [OFInvalidArgumentException exception]; + + memcpy(node, address->sockaddr.ipx.sipx_node, IPX_NODE_LEN); +} Index: src/unicode.m ================================================================== --- src/unicode.m +++ src/unicode.m @@ -641,16 +641,16 @@ 0, 42912, 0, 42914, 0, 42916, 0, 42918, 0, 42920, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42932, 0, 42934, 0, 42936, 0, 42938, 0, 42940, 0, 42942, 0, 0, 0, 42946, 0, 0, 0, 0, + 42951, 0, 42953, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 42997, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const of_unichar_t uppercasePage171[0x100] = { 0, 0, 0, 0, 0, 0, 0, 0, @@ -1445,17 +1445,17 @@ 42905, 0, 42907, 0, 42909, 0, 42911, 0, 42913, 0, 42915, 0, 42917, 0, 42919, 0, 42921, 0, 614, 604, 609, 620, 618, 0, 670, 647, 669, 43859, 42933, 0, 42935, 0, 42937, 0, 42939, 0, 42941, 0, 42943, 0, - 0, 0, 42947, 0, 42900, 642, 7566, 0, + 0, 0, 42947, 0, 42900, 642, 7566, 42952, + 0, 42954, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 42998, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static const of_unichar_t lowercasePage255[0x100] = { 0, 0, 0, 0, 0, 0, 0, 0, @@ -5944,10 +5944,141 @@ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, }; + +static const char *const decompositionPage281[0x100] = { + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + "\xF0\x91\xA4\xB5\xF0\x91\xA4\xB0", NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, +}; static const char *const decompositionPage465[0x100] = { NULL, NULL, NULL, NULL, NULL, NULL, @@ -10321,11 +10452,11 @@ "\xC9\xAB", "\xEA\xAD\x92", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, + NULL, "\xCA\x8D", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -12101,10 +12232,141 @@ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, }; + +static const char *const decompCompatPage507[0x100] = { + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + NULL, NULL, + "\x30", "\x31", + "\x32", "\x33", + "\x34", "\x35", + "\x36", "\x37", + "\x38", "\x39", + NULL, NULL, + NULL, NULL, + NULL, NULL, +}; const of_unichar_t *const of_unicode_uppercase_table[0x1EA] = { uppercasePage0, uppercasePage1, uppercasePage2, uppercasePage3, uppercasePage4, uppercasePage5, emptyPage, emptyPage, emptyPage, emptyPage, emptyPage, emptyPage, @@ -12741,11 +13003,11 @@ emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, decompositionPage272, decompositionPage273, emptyDecompositionPage, decompositionPage275, decompositionPage276, decompositionPage277, emptyDecompositionPage, - emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, + emptyDecompositionPage, emptyDecompositionPage, decompositionPage281, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, @@ -12999,11 +13261,11 @@ emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, decompositionPage272, decompositionPage273, emptyDecompositionPage, decompositionPage275, decompositionPage276, decompositionPage277, emptyDecompositionPage, - emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, + emptyDecompositionPage, emptyDecompositionPage, decompositionPage281, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, @@ -13075,11 +13337,11 @@ emptyDecompositionPage, emptyDecompositionPage, decompCompatPage494, emptyDecompositionPage, emptyDecompositionPage, decompCompatPage497, decompCompatPage498, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, - emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, + decompCompatPage507, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, emptyDecompositionPage, Index: tests/Makefile ================================================================== --- tests/Makefile +++ tests/Makefile @@ -9,11 +9,11 @@ PROG_NOINST = tests${PROG_SUFFIX} STATIC_LIB_NOINST = ${TESTS_STATIC_LIB} SRCS = ForwardingTests.m \ OFArrayTests.m \ - ${OFBLOCKTESTS_M} \ + ${OF_BLOCK_TESTS_M} \ OFCharacterSetTests.m \ OFDataTests.m \ OFDateTests.m \ OFDictionaryTests.m \ OFInvocationTests.m \ @@ -50,19 +50,25 @@ OFSHA1HashTests.m \ OFSHA224HashTests.m \ OFSHA256HashTests.m \ OFSHA384HashTests.m \ OFSHA512HashTests.m +SRCS_IPX = OFIPXSocketTests.m \ + OFSPXSocketTests.m \ + OFSPXStreamSocketTests.m SRCS_PLUGINS = OFPluginTests.m +SRCS_SCTP = OFSCTPSocketTests.m SRCS_SOCKETS = OFDNSResolverTests.m \ - ${OFHTTPCLIENTTESTS_M} \ + ${OF_HTTP_CLIENT_TESTS_M} \ OFHTTPCookieTests.m \ OFHTTPCookieManagerTests.m \ OFKernelEventObserverTests.m \ OFTCPSocketTests.m \ OFUDPSocketTests.m \ - SocketTests.m + SocketTests.m \ + ${USE_SRCS_IPX} \ + ${USE_SRCS_SCTP} SRCS_THREADS = OFThreadTests.m SRCS_WINDOWS = OFWindowsRegistryKeyTests.m IOS_USER ?= mobile IOS_TMP ?= /tmp/objfw-test @@ -73,25 +79,25 @@ .PHONY: run run-on-ios run-on-android run: rm -f libobjfw.so.${OBJFW_LIB_MAJOR} rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} - rm -f libobjfw.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib + rm -f objfw.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR} rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} - rm -f libobjfwrt.dll libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib + rm -f objfwrt.dll libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib rm -f ${OBJFWRT_AMIGA_LIB} if test -f ../src/libobjfw.so; then \ ${LN_S} ../src/libobjfw.so libobjfw.so.${OBJFW_LIB_MAJOR}; \ ${LN_S} ../src/libobjfw.so \ libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ elif test -f ../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; then \ ${LN_S} ../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} \ libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ fi - if test -f ../src/libobjfw.dll; then \ - ${LN_S} ../src/libobjfw.dll libobjfw.dll; \ + if test -f ../src/objfw.dll; then \ + ${LN_S} ../src/objfw.dll objfw.dll; \ fi if test -f ../src/libobjfw.dylib; then \ ${LN_S} ../src/libobjfw.dylib \ libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ fi @@ -101,12 +107,12 @@ ${LN_S} ../src/runtime/libobjfwrt.so \ libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ elif test -f ../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; then \ ${LN_S} ../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ fi - if test -f ../src/runtime/libobjfwrt.dll; then \ - ${LN_S} ../src/runtime/libobjfwrt.dll libobjfwrt.dll; \ + if test -f ../src/runtime/objfwrt.dll; then \ + ${LN_S} ../src/runtime/objfwrt.dll objfwrt.dll; \ fi if test -f ../src/runtime/libobjfwrt.dylib; then \ ${LN_S} ../src/runtime/libobjfwrt.dylib \ libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ fi @@ -119,14 +125,14 @@ DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ LIBRARY_PATH=.$${LIBRARY_PATH+:}$$LIBRARY_PATH \ ASAN_OPTIONS=allocator_may_return_null=1 \ ${WRAPPER} ./${PROG_NOINST}; EXIT=$$?; \ rm -f libobjfw.so.${OBJFW_LIB_MAJOR}; \ - rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} libobjfw.dll; \ + rm -f objfw.so.${OBJFW_LIB_MAJOR_MINOR} objfw.dll; \ rm -f libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR}; \ - rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} libobjfwrt.dll; \ + rm -f objfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} objfwrt.dll; \ rm -f libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ exit $$EXIT run-on-ios: all if [ -z "${IOS_HOST}" ]; then \ Index: tests/OFDNSResolverTests.m ================================================================== --- tests/OFDNSResolverTests.m +++ tests/OFDNSResolverTests.m @@ -25,10 +25,12 @@ - (void)DNSResolverTests { void *pool = objc_autoreleasePoolPush(); OFDNSResolver *resolver = [OFDNSResolver resolver]; OFMutableString *staticHosts = [OFMutableString string]; + + of_stdout.foregroundColor = [OFColor lime]; for (OFString *host in resolver.staticHosts) { OFString *IPs; if (staticHosts.length > 0) @@ -37,30 +39,37 @@ IPs = [[resolver.staticHosts objectForKey: host] componentsJoinedByString: @", "]; [staticHosts appendFormat: @"%@=(%@)", host, IPs]; } - PRINT(GREEN, @"Static hosts: %@", staticHosts); - - PRINT(GREEN, @"Name servers: %@", - [resolver.nameServers componentsJoinedByString: @", "]); - - PRINT(GREEN, @"Local domain: %@", resolver.localDomain); - - PRINT(GREEN, @"Search domains: %@", - [resolver.searchDomains componentsJoinedByString: @", "]); - - PRINT(GREEN, @"Timeout: %lf", resolver.timeout); - - PRINT(GREEN, @"Max attempts: %u", resolver.maxAttempts); - - PRINT(GREEN, @"Min number of dots in absolute name: %u", - resolver.minNumberOfDotsInAbsoluteName); - - PRINT(GREEN, @"Uses TCP: %u", resolver.usesTCP); - - PRINT(GREEN, @"Config reload interval: %lf", - resolver.configReloadInterval); + [of_stdout writeFormat: @"[OFDNSResolver] Static hosts: %@\n", + staticHosts]; + + [of_stdout writeFormat: @"[OFDNSResolver] Name servers: %@\n", + [resolver.nameServers componentsJoinedByString: @", "]]; + + [of_stdout writeFormat: @"[OFDNSResolver] Local domain: %@\n", + resolver.localDomain]; + + [of_stdout writeFormat: @"[OFDNSResolver] Search domains: %@\n", + [resolver.searchDomains componentsJoinedByString: @", "]]; + + [of_stdout writeFormat: @"[OFDNSResolver] Timeout: %lf\n", + resolver.timeout]; + + [of_stdout writeFormat: @"[OFDNSResolver] Max attempts: %u\n", + resolver.maxAttempts]; + + [of_stdout writeFormat: + @"[OFDNSResolver] Min number of dots in absolute name: %u\n", + resolver.minNumberOfDotsInAbsoluteName]; + + [of_stdout writeFormat: @"[OFDNSResolver] Uses TCP: %u\n", + module, resolver.usesTCP]; + + [of_stdout writeFormat: + @"[OFDNSResolver] Config reload interval: %lf\n", + resolver.configReloadInterval]; objc_autoreleasePoolPop(pool); } @end Index: tests/OFDateTests.m ================================================================== --- tests/OFDateTests.m +++ tests/OFDateTests.m @@ -55,21 +55,28 @@ EXPECT_EXCEPTION(@"Detection of unparsed in " @"+[dateWithDateString:format:]", OFInvalidFormatException, [OFDate dateWithDateString: @"2000-06-20T12:34:56+0200x" format: @"%Y-%m-%dT%H:%M:%S%z"]) - /* - * We can only test local dates that specify a time zone, as the local - * time zone differs between systems. - */ + TEST(@"+[dateWithLocalDateString:format:]", + [[[OFDate dateWithLocalDateString: @"2000-06-20T12:34:56" + format: @"%Y-%m-%dT%H:%M:%S"] + localDateStringWithFormat: @"%Y-%m-%dT%H:%M:%S"] + isEqual: @"2000-06-20T12:34:56"]); + TEST(@"+[dateWithLocalDateString:format:]", [[[OFDate dateWithLocalDateString: @"2000-06-20T12:34:56-0200" format: @"%Y-%m-%dT%H:%M:%S%z"] description] isEqual: @"2000-06-20T14:34:56Z"]); EXPECT_EXCEPTION(@"Detection of unparsed in " - @"+[dateWithLocalDateString:format:]", OFInvalidFormatException, + @"+[dateWithLocalDateString:format:] #1", OFInvalidFormatException, + [OFDate dateWithLocalDateString: @"2000-06-20T12:34:56x" + format: @"%Y-%m-%dT%H:%M:%S"]) + + EXPECT_EXCEPTION(@"Detection of unparsed in " + @"+[dateWithLocalDateString:format:] #2", OFInvalidFormatException, [OFDate dateWithLocalDateString: @"2000-06-20T12:34:56+0200x" format: @"%Y-%m-%dT%H:%M:%S%z"]) TEST(@"-[isEqual:]", [d1 isEqual: [OFDate dateWithTimeIntervalSince1970: 0]] && ADDED tests/OFIPXSocketTests.m Index: tests/OFIPXSocketTests.m ================================================================== --- tests/OFIPXSocketTests.m +++ tests/OFIPXSocketTests.m @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "TestsAppDelegate.h" + +static OFString *module = @"OFIPXSocket"; + +@implementation TestsAppDelegate (OFIPXSocketTests) +- (void)IPXSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFIPXSocket *sock; + of_socket_address_t address1, address2; + char buffer[5]; + + TEST(@"+[socket]", (sock = [OFIPXSocket socket])) + + @try { + TEST(@"-[bindToPort:packetType:]", + R(address1 = [sock bindToPort: 0 + packetType: 0])) + } @catch (OFBindFailedException *e) { + switch (e.errNo) { + case EAFNOSUPPORT: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFIPXSocket] -[bindToPort:packetType:]: " + @"IPX unsupported, skipping tests"]; + break; + case EADDRNOTAVAIL: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFIPXSocket] -[bindToPort:packetType:]: " + @"IPX not configured, skipping tests"]; + break; + default: + @throw e; + } + + objc_autoreleasePoolPop(pool); + return; + } + + TEST(@"-[sendBuffer:length:receiver:]", + R([sock sendBuffer: "Hello" + length: 5 + receiver: &address1])) + + TEST(@"-[receiveIntoBuffer:length:sender:]", + [sock receiveIntoBuffer: buffer + length: 5 + sender: &address2] == 5 && + memcmp(buffer, "Hello", 5) == 0 && + of_socket_address_equal(&address1, &address2) && + of_socket_address_hash(&address1) == + of_socket_address_hash(&address2)) + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/OFLocaleTests.m ================================================================== --- tests/OFLocaleTests.m +++ tests/OFLocaleTests.m @@ -17,24 +17,27 @@ #include "config.h" #import "TestsAppDelegate.h" -static OFString *module = @"OFLocale"; - @implementation TestsAppDelegate (OFLocaleTests) - (void)localeTests { void *pool = objc_autoreleasePoolPush(); - PRINT(GREEN, @"Language: %@", [OFLocale language]); - - PRINT(GREEN, @"Territory: %@", [OFLocale territory]); - - PRINT(GREEN, @"Encoding: %@", - of_string_name_of_encoding([OFLocale encoding])); - - PRINT(GREEN, @"Decimal point: %@", [OFLocale decimalPoint]); + of_stdout.foregroundColor = [OFColor lime]; + + [of_stdout writeFormat: @"[OFLocale]: Language: %@\n", + [OFLocale language]]; + + [of_stdout writeFormat: @"[OFLocale]: Territory: %@\n", + [OFLocale territory]]; + + [of_stdout writeFormat: @"[OFLocale]: Encoding: %@\n", + of_string_name_of_encoding([OFLocale encoding])]; + + [of_stdout writeFormat: @"[OFLocale]: Decimal point: %@\n", + [OFLocale decimalPoint]]; objc_autoreleasePoolPop(pool); } @end ADDED tests/OFSCTPSocketTests.m Index: tests/OFSCTPSocketTests.m ================================================================== --- tests/OFSCTPSocketTests.m +++ tests/OFSCTPSocketTests.m @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#import "TestsAppDelegate.h" + +static OFString *module = @"OFSCTPSocket"; + +@implementation TestsAppDelegate (OFSCTPSocketTests) +- (void)SCTPSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFSCTPSocket *server, *client = nil, *accepted; + uint16_t port; + char buf[6]; + + TEST(@"+[socket]", (server = [OFSCTPSocket socket]) && + (client = [OFSCTPSocket socket])) + + @try { + TEST(@"-[bindToHost:port:]", + (port = [server bindToHost: @"127.0.0.1" + port: 0])) + } @catch (OFBindFailedException *e) { + switch (e.errNo) { + case EPROTONOSUPPORT: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFSCTPSocket] -[bindToHost:port:]: " + @"SCTP unsupported, skipping tests"]; + break; + default: + @throw e; + } + + objc_autoreleasePoolPop(pool); + return; + } + + TEST(@"-[listen]", R([server listen])) + + TEST(@"-[connectToHost:port:]", + R([client connectToHost: @"127.0.0.1" + port: port])) + + TEST(@"-[accept]", (accepted = [server accept])) + + TEST(@"-[remoteAddress]", + [of_socket_address_ip_string(accepted.remoteAddress, NULL) + isEqual: @"127.0.0.1"]) + + TEST(@"-[sendBuffer:length:]", R([client sendBuffer: "Hello!" + length: 6])) + + TEST(@"-[receiveIntoBuffer:length:]", [accepted receiveIntoBuffer: buf + length: 6] && + !memcmp(buf, "Hello!", 6)) + + objc_autoreleasePoolPop(pool); +} +@end ADDED tests/OFSPXSocketTests.m Index: tests/OFSPXSocketTests.m ================================================================== --- tests/OFSPXSocketTests.m +++ tests/OFSPXSocketTests.m @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "TestsAppDelegate.h" + +static OFString *module = @"OFSPXSocket"; + +@interface SPXSocketDelegate: OFObject +{ +@public + OFSequencedPacketSocket *_expectedServerSocket; + OFSPXSocket *_expectedClientSocket; + unsigned char _expectedNode[IPX_NODE_LEN]; + uint32_t _expectedNetwork; + uint16_t _expectedPort; + bool _accepted; + bool _connected; +} +@end + +@implementation SPXSocketDelegate +- (bool)socket: (OFSequencedPacketSocket *)sock + didAcceptSocket: (OFSequencedPacketSocket *)accepted + exception: (id)exception +{ + OF_ENSURE(!_accepted); + + _accepted = (sock == _expectedServerSocket && accepted != nil && + exception == nil); + + if (_accepted && _connected) + [[OFRunLoop mainRunLoop] stop]; + + return false; +} + +- (void)socket: (OFSPXSocket *)sock + didConnectToNode: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + exception: (id)exception +{ + OF_ENSURE(!_connected); + + _connected = (sock == _expectedClientSocket && + memcmp(node, _expectedNode, IPX_NODE_LEN) == 0 && + network == _expectedNetwork && port == _expectedPort && + exception == nil); + + if (_accepted && _connected) + [[OFRunLoop mainRunLoop] stop]; +} +@end + +@implementation TestsAppDelegate (OFSPXSocketTests) +- (void)SPXSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFSPXSocket *sockClient, *sockServer, *sockAccepted;; + of_socket_address_t address1; + const of_socket_address_t *address2; + unsigned char node[IPX_NODE_LEN], node2[IPX_NODE_LEN]; + uint32_t network; + uint16_t port; + char buffer[5]; + SPXSocketDelegate *delegate; + + TEST(@"+[socket]", (sockClient = [OFSPXSocket socket]) && + (sockServer = [OFSPXSocket socket])) + + @try { + TEST(@"-[bindToPort:]", + R(address1 = [sockServer bindToPort: 0])) + } @catch (OFBindFailedException *e) { + switch (e.errNo) { + case EAFNOSUPPORT: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFSPXSocket] -[bindToPort:]: " + @"IPX unsupported, skipping tests"]; + break; + case ESOCKTNOSUPPORT: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFSPXSocket] -[bindToPort:]: " + @"SPX unsupported, skipping tests"]; + break; + case EADDRNOTAVAIL: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFSPXSocket] -[bindToPort:]: " + @"IPX not configured, skipping tests"]; + break; + default: + @throw e; + } + + objc_autoreleasePoolPop(pool); + return; + } + + of_socket_address_get_ipx_node(&address1, node); + network = of_socket_address_get_ipx_network(&address1); + port = of_socket_address_get_port(&address1); + + TEST(@"-[listen]", R([sockServer listen])) + + TEST(@"-[connectToNode:network:port:]", + R([sockClient connectToNode: node + network: network + port: port])) + + TEST(@"-[accept]", (sockAccepted = [sockServer accept])) + + TEST(@"-[sendBuffer:length:]", + R([sockAccepted sendBuffer: "Hello" + length: 5])) + + TEST(@"-[receiveIntoBuffer:length:]", + [sockClient receiveIntoBuffer: buffer + length: 5] == 5 && + memcmp(buffer, "Hello", 5) == 0) + + TEST(@"-[remoteAddress]", + (address2 = sockAccepted.remoteAddress) && + R(of_socket_address_get_ipx_node(address2, node2)) && + memcmp(node, node2, IPX_NODE_LEN) == 0 && + of_socket_address_get_ipx_network(address2) == network) + + delegate = [[[SPXSocketDelegate alloc] init] autorelease]; + + sockServer = [OFSPXSocket socket]; + delegate->_expectedServerSocket = sockServer; + sockServer.delegate = delegate; + + sockClient = [OFSPXSocket socket]; + delegate->_expectedClientSocket = sockClient; + sockClient.delegate = delegate; + + address1 = [sockServer bindToPort: 0]; + [sockServer listen]; + [sockServer asyncAccept]; + + of_socket_address_get_ipx_node(&address1, node); + memcpy(delegate->_expectedNode, node, IPX_NODE_LEN); + delegate->_expectedNetwork = network = + of_socket_address_get_ipx_network(&address1); + delegate->_expectedPort = port = of_socket_address_get_port(&address1); + + [sockClient asyncConnectToNode: node + network: network + port: port]; + + [[OFRunLoop mainRunLoop] runUntilDate: + [OFDate dateWithTimeIntervalSinceNow: 2]]; + + TEST(@"-[asyncAccept] & -[asyncConnectToNode:network:port:]", + delegate->_accepted && delegate->_connected) + + objc_autoreleasePoolPop(pool); +} +@end ADDED tests/OFSPXStreamSocketTests.m Index: tests/OFSPXStreamSocketTests.m ================================================================== --- tests/OFSPXStreamSocketTests.m +++ tests/OFSPXStreamSocketTests.m @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "TestsAppDelegate.h" + +static OFString *module = @"OFSPXStreamSocket"; + +@interface SPXStreamSocketDelegate: OFObject +{ +@public + OFStreamSocket *_expectedServerSocket; + OFSPXStreamSocket *_expectedClientSocket; + unsigned char _expectedNode[IPX_NODE_LEN]; + uint32_t _expectedNetwork; + uint16_t _expectedPort; + bool _accepted; + bool _connected; +} +@end + +@implementation SPXStreamSocketDelegate +- (bool)socket: (OFStreamSocket *)sock + didAcceptSocket: (OFStreamSocket *)accepted + exception: (id)exception +{ + OF_ENSURE(!_accepted); + + _accepted = (sock == _expectedServerSocket && accepted != nil && + exception == nil); + + if (_accepted && _connected) + [[OFRunLoop mainRunLoop] stop]; + + return false; +} + +- (void)socket: (OFSPXStreamSocket *)sock + didConnectToNode: (unsigned char [IPX_NODE_LEN])node + network: (uint32_t)network + port: (uint16_t)port + exception: (id)exception +{ + OF_ENSURE(!_connected); + + _connected = (sock == _expectedClientSocket && + memcmp(node, _expectedNode, IPX_NODE_LEN) == 0 && + network == _expectedNetwork && port == _expectedPort && + exception == nil); + + if (_accepted && _connected) + [[OFRunLoop mainRunLoop] stop]; +} +@end + +@implementation TestsAppDelegate (OFSPXStreamSocketTests) +- (void)SPXStreamSocketTests +{ + void *pool = objc_autoreleasePoolPush(); + OFSPXStreamSocket *sockClient, *sockServer, *sockAccepted;; + of_socket_address_t address1; + const of_socket_address_t *address2; + unsigned char node[IPX_NODE_LEN], node2[IPX_NODE_LEN]; + uint32_t network; + uint16_t port; + char buffer[5]; + SPXStreamSocketDelegate *delegate; + + TEST(@"+[socket]", (sockClient = [OFSPXStreamSocket socket]) && + (sockServer = [OFSPXStreamSocket socket])) + + @try { + TEST(@"-[bindToPort:]", + R(address1 = [sockServer bindToPort: 0])) + } @catch (OFBindFailedException *e) { + switch (e.errNo) { + case EAFNOSUPPORT: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFSPXStreamSocket] -[bindToPort:]: " + @"IPX unsupported, skipping tests"]; + break; + case ESOCKTNOSUPPORT: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFSPXStreamSocket] -[bindToPort:]: " + @"SPX unsupported, skipping tests"]; + break; + case EADDRNOTAVAIL: + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout writeLine: + @"[OFSPXStreamSocket] -[bindToPort:]: " + @"IPX not configured, skipping tests"]; + break; + default: + @throw e; + } + + objc_autoreleasePoolPop(pool); + return; + } + + of_socket_address_get_ipx_node(&address1, node); + network = of_socket_address_get_ipx_network(&address1); + port = of_socket_address_get_port(&address1); + + TEST(@"-[listen]", R([sockServer listen])) + + TEST(@"-[connectToNode:network:port:]", + R([sockClient connectToNode: node + network: network + port: port])) + + TEST(@"-[accept]", (sockAccepted = [sockServer accept])) + + /* Test reassembly (this would not work with OFSPXSocket) */ + TEST(@"-[writeBuffer:length:]", + R([sockAccepted writeBuffer: "Hello" + length: 5])) + + TEST(@"-[readIntoBuffer:length:]", + [sockClient readIntoBuffer: buffer + length: 2] == 2 && + memcmp(buffer, "He", 2) == 0 && + [sockClient readIntoBuffer: buffer + length: 3] == 3 && + memcmp(buffer, "llo", 3) == 0) + + TEST(@"-[remoteAddress]", + (address2 = sockAccepted.remoteAddress) && + R(of_socket_address_get_ipx_node(address2, node2)) && + memcmp(node, node2, IPX_NODE_LEN) == 0 && + of_socket_address_get_ipx_network(address2) == network) + + delegate = [[[SPXStreamSocketDelegate alloc] init] autorelease]; + + sockServer = [OFSPXStreamSocket socket]; + delegate->_expectedServerSocket = sockServer; + sockServer.delegate = delegate; + + sockClient = [OFSPXStreamSocket socket]; + delegate->_expectedClientSocket = sockClient; + sockClient.delegate = delegate; + + address1 = [sockServer bindToPort: 0]; + [sockServer listen]; + [sockServer asyncAccept]; + + of_socket_address_get_ipx_node(&address1, node); + memcpy(delegate->_expectedNode, node, IPX_NODE_LEN); + delegate->_expectedNetwork = network = + of_socket_address_get_ipx_network(&address1); + delegate->_expectedPort = port = of_socket_address_get_port(&address1); + + [sockClient asyncConnectToNode: node + network: network + port: port]; + + [[OFRunLoop mainRunLoop] runUntilDate: + [OFDate dateWithTimeIntervalSinceNow: 2]]; + + TEST(@"-[asyncAccept] & -[asyncConnectToNode:network:port:]", + delegate->_accepted && delegate->_connected) + + objc_autoreleasePoolPop(pool); +} +@end Index: tests/OFSystemInfoTests.m ================================================================== --- tests/OFSystemInfoTests.m +++ tests/OFSystemInfoTests.m @@ -17,85 +17,104 @@ #include "config.h" #import "TestsAppDelegate.h" -static OFString *module = @"OFSystemInfo"; - @implementation TestsAppDelegate (OFSystemInfoTests) - (void)systemInfoTests { void *pool = objc_autoreleasePoolPush(); #ifdef OF_HAVE_FILES OFString *userConfigPath, *userDataPath; #endif - PRINT(GREEN, @"Page size: %zd", [OFSystemInfo pageSize]); - - PRINT(GREEN, @"Number of CPUs: %zd", [OFSystemInfo numberOfCPUs]); - - PRINT(GREEN, @"ObjFW version: %@", [OFSystemInfo ObjFWVersion]); - - PRINT(GREEN, @"ObjFW version major: %u", - [OFSystemInfo ObjFWVersionMajor]); - - PRINT(GREEN, @"ObjFW version minor: %u", - [OFSystemInfo ObjFWVersionMinor]); - - PRINT(GREEN, @"Operating system name: %@", - [OFSystemInfo operatingSystemName]); - - PRINT(GREEN, @"Operating system version: %@", - [OFSystemInfo operatingSystemVersion]); + of_stdout.foregroundColor = [OFColor lime]; + + [of_stdout writeFormat: @"[OFSystemInfo] Page size: %zd\n", + [OFSystemInfo pageSize]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Number of CPUs: %zd\n", + [OFSystemInfo numberOfCPUs]]; + + [of_stdout writeFormat: @"[OFSystemInfo] ObjFW version: %@\n", + [OFSystemInfo ObjFWVersion]]; + + [of_stdout writeFormat: @"[OFSystemInfo] ObjFW version major: %u\n", + [OFSystemInfo ObjFWVersionMajor]]; + + [of_stdout writeFormat: @"[OFSystemInfo] ObjFW version minor: %u\n", + [OFSystemInfo ObjFWVersionMinor]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Operating system name: %@\n", + [OFSystemInfo operatingSystemName]]; + + [of_stdout writeFormat: + @"[OFSystemInfo] Operating system version: %@\n", + [OFSystemInfo operatingSystemVersion]]; #ifdef OF_HAVE_FILES @try { userConfigPath = [OFSystemInfo userConfigPath]; } @catch (OFNotImplementedException *e) { userConfigPath = @"Not implemented"; } - PRINT(GREEN, @"User config path: %@", userConfigPath); + [of_stdout writeFormat: @"[OFSystemInfo] User config path: %@\n", + userConfigPath]; @try { userDataPath = [OFSystemInfo userDataPath]; } @catch (OFNotImplementedException *e) { userDataPath = @"Not implemented"; } - PRINT(GREEN, @"User data path: %@", userDataPath); + [of_stdout writeFormat: @"[OFSystemInfo] User data path: %@\n", + userDataPath]; #endif - PRINT(GREEN, @"CPU vendor: %@", [OFSystemInfo CPUVendor]); + [of_stdout writeFormat: @"[OFSystemInfo] CPU vendor: %@\n", + [OFSystemInfo CPUVendor]]; - PRINT(GREEN, @"CPU model: %@", [OFSystemInfo CPUModel]); + [of_stdout writeFormat: @"[OFSystemInfo] CPU model: %@\n", + [OFSystemInfo CPUModel]]; #if defined(OF_X86_64) || defined(OF_X86) - PRINT(GREEN, @"Supports MMX: %d", [OFSystemInfo supportsMMX]); - - PRINT(GREEN, @"Supports SSE: %d", [OFSystemInfo supportsSSE]); - - PRINT(GREEN, @"Supports SSE2: %d", [OFSystemInfo supportsSSE2]); - - PRINT(GREEN, @"Supports SSE3: %d", [OFSystemInfo supportsSSE3]); - - PRINT(GREEN, @"Supports SSSE3: %d", [OFSystemInfo supportsSSSE3]); - - PRINT(GREEN, @"Supports SSE4.1: %d", [OFSystemInfo supportsSSE41]); - - PRINT(GREEN, @"Supports SSE4.2: %d", [OFSystemInfo supportsSSE42]); - - PRINT(GREEN, @"Supports AVX: %d", [OFSystemInfo supportsAVX]); - - PRINT(GREEN, @"Supports AVX2: %d", [OFSystemInfo supportsAVX2]); - - PRINT(GREEN, @"Supports AES-NI: %d", [OFSystemInfo supportsAESNI]); - - PRINT(GREEN, @"Supports SHA extensions: %d", - [OFSystemInfo supportsSHAExtensions]); + [of_stdout writeFormat: @"[OFSystemInfo] Supports MMX: %d\n", + [OFSystemInfo supportsMMX]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports SSE: %d\n", + [OFSystemInfo supportsSSE]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports SSE2: %d\n", + [OFSystemInfo supportsSSE2]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports SSE3: %d\n", + [OFSystemInfo supportsSSE3]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports SSSE3: %d\n", + [OFSystemInfo supportsSSSE3]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports SSE4.1: %d\n", + [OFSystemInfo supportsSSE41]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports SSE4.2: %d\n", + [OFSystemInfo supportsSSE42]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports AVX: %d\n", + [OFSystemInfo supportsAVX]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports AVX2: %d\n", + [OFSystemInfo supportsAVX2]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports AES-NI: %d\n", + [OFSystemInfo supportsAESNI]]; + + [of_stdout writeFormat: @"[OFSystemInfo] Supports SHA extensions: %d\n", + [OFSystemInfo supportsSHAExtensions]]; #endif #ifdef OF_POWERPC - PRINT(GREEN, @"Supports AltiVec: %d", [OFSystemInfo supportsAltiVec]); + [of_stdout writeFormat: @"[OFSystemInfo] Supports AltiVec: %d\n", + [OFSystemInfo supportsAltiVec]]; #endif objc_autoreleasePoolPop(pool); } @end Index: tests/OFWindowsRegistryKeyTests.m ================================================================== --- tests/OFWindowsRegistryKeyTests.m +++ tests/OFWindowsRegistryKeyTests.m @@ -27,11 +27,10 @@ void *pool = objc_autoreleasePoolPush(); OFData *data = [OFData dataWithItems: "abcdef" count: 6]; OFWindowsRegistryKey *softwareKey, *ObjFWKey; DWORD type; - OFString *string; TEST(@"+[OFWindowsRegistryKey classesRootKey]", [OFWindowsRegistryKey classesRootKey]) TEST(@"+[OFWindowsRegistryKey currentConfigKey]", @@ -62,14 +61,12 @@ R([ObjFWKey setData: data forValue: @"data" type: REG_BINARY])) TEST(@"-[dataForValue:subkeyPath:flags:type:]", - [[softwareKey dataForValue: @"data" - subkeyPath: @"ObjFW" - flags: RRF_RT_REG_BINARY - type: &type] isEqual: data] && + [[ObjFWKey dataForValue: @"data" + type: &type] isEqual: data] && type == REG_BINARY) TEST(@"-[setString:forValue:type:]", R([ObjFWKey setString: @"foobar" forValue: @"string"]) && @@ -76,25 +73,18 @@ R([ObjFWKey setString: @"%PATH%;foo" forValue: @"expand" type: REG_EXPAND_SZ])) TEST(@"-[stringForValue:subkeyPath:]", - [[softwareKey stringForValue: @"string" - subkeyPath: @"ObjFW"] isEqual: @"foobar"] && - [[softwareKey stringForValue: @"expand" - subkeyPath: @"ObjFW" - flags: RRF_RT_REG_EXPAND_SZ | RRF_NOEXPAND - type: &type] isEqual: @"%PATH%;foo"] && - type == REG_EXPAND_SZ && - (string = [ObjFWKey stringForValue: @"expand" - subkeyPath: nil]) && - ![string isEqual: @"%PATH%;foo"] && - [string hasSuffix: @";foo"]) + [[ObjFWKey stringForValue: @"string"] isEqual: @"foobar"] && + [[ObjFWKey stringForValue: @"expand" + type: &type] isEqual: @"%PATH%;foo"] && + type == REG_EXPAND_SZ) TEST(@"-[deleteValue:]", R([ObjFWKey deleteValue: @"data"])) TEST(@"-[deleteSubkeyAtPath:]", R([softwareKey deleteSubkeyAtPath: @"ObjFW"])) objc_autoreleasePoolPop(pool); } @end Index: tests/TestsAppDelegate.h ================================================================== --- tests/TestsAppDelegate.h +++ tests/TestsAppDelegate.h @@ -52,34 +52,18 @@ inModule: module]; \ _fails++; \ } \ } #define R(...) (__VA_ARGS__, 1) -#define PRINT(color, fmt, ...) \ - { \ - OFString *msg = [OFString stringWithFormat: \ - @"[%@] " fmt @"\n", module, __VA_ARGS__]; \ - [self outputString: msg \ - inColor: color]; \ - } @class OFString; -enum { - NO_COLOR, - RED, - GREEN, - YELLOW -}; - @interface TestsAppDelegate: OFObject { int _fails; } -- (void)outputString: (OFString *)str - inColor: (int)color; - (void)outputTesting: (OFString *)test inModule: (OFString *)module; - (void)outputSuccess: (OFString *)test inModule: (OFString *)module; - (void)outputFailure: (OFString *)test @@ -131,10 +115,14 @@ @end @interface TestsAppDelegate (OFINIFileTests) - (void)INIFileTests; @end + +@interface TestsAppDelegate (OFIPXSocketTests) +- (void)IPXSocketTests; +@end @interface TestsAppDelegate (OFInvocationTests) - (void)invocationTests; @end @@ -188,18 +176,10 @@ @interface TestsAppDelegate (ScryptTests) - (void)scryptTests; @end -@interface TestsAppDelegate (OFSerializationTests) -- (void)serializationTests; -@end - -@interface TestsAppDelegate (OFSetTests) -- (void)setTests; -@end - @interface TestsAppDelegate (OFSHA1HashTests) - (void)SHA1HashTests; @end @interface TestsAppDelegate (OFSHA224HashTests) @@ -215,10 +195,30 @@ @end @interface TestsAppDelegate (OFSHA512HashTests) - (void)SHA512HashTests; @end + +@interface TestsAppDelegate (OFSCTPSocketTests) +- (void)SCTPSocketTests; +@end + +@interface TestsAppDelegate (OFSPXSocketTests) +- (void)SPXSocketTests; +@end + +@interface TestsAppDelegate (OFSPXStreamSocketTests) +- (void)SPXStreamSocketTests; +@end + +@interface TestsAppDelegate (OFSerializationTests) +- (void)serializationTests; +@end + +@interface TestsAppDelegate (OFSetTests) +- (void)setTests; +@end @interface TestsAppDelegate (OFSystemInfoTests) - (void)systemInfoTests; @end Index: tests/TestsAppDelegate.m ================================================================== --- tests/TestsAppDelegate.m +++ tests/TestsAppDelegate.m @@ -19,16 +19,10 @@ #include #import "TestsAppDelegate.h" -#if defined(STDOUT) && (defined(OF_WINDOWS) || defined(OF_MSDOS) || \ - defined(OF_IOS)) -# undef STDOUT -# define STDOUT_SIMPLE -#endif - #ifdef OF_IOS # include #endif #ifdef OF_PSP @@ -145,25 +139,24 @@ defined(OF_NINTENDO_3DS) @try { return of_application_main(&argc, &argv, [[TestsAppDelegate alloc] init]); } @catch (id e) { - TestsAppDelegate *delegate = - [OFApplication sharedApplication].delegate; OFString *string = [OFString stringWithFormat: @"\nRuntime error: Unhandled exception:\n%@\n", e]; OFString *backtrace = [OFString stringWithFormat: @"\nBacktrace:\n %@\n\n", [[e backtrace] componentsJoinedByString: @"\n "]]; - [delegate outputString: string - inColor: RED]; - [delegate outputString: backtrace - inColor: RED]; + of_stdout.foregroundColor = [OFColor red]; + [of_stdout writeString: string]; + [of_stdout writeString: backtrace]; + # if defined(OF_WII) - [delegate outputString: @"Press home button to exit!\n" - inColor: NO_COLOR]; + [of_stdout reset]; + [of_stdout writeString: @"Press home button to exit!"]; + for (;;) { WPAD_ScanPads(); if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) [OFApplication terminateWithStatus: 1]; @@ -171,21 +164,23 @@ VIDEO_WaitVSync(); } # elif defined(OF_PSP) sceKernelSleepThreadCB(); # elif defined(OF_NINTENDO_DS) - [delegate outputString: @"Press start button to exit!" - inColor: NO_COLOR]; + [of_stdout reset]; + [of_stdout writeString: @"Press start button to exit!"]; + for (;;) { swiWaitForVBlank(); scanKeys(); if (keysDown() & KEY_START) [OFApplication terminateWithStatus: 1]; } # elif defined(OF_NINTENDO_3DS) - [delegate outputString: @"Press start button to exit!" - inColor: NO_COLOR]; + [of_stdout reset]; + [of_stdout writeString: @"Press start button to exit!"]; + for (;;) { hidScanInput(); if (hidKeysDown() & KEY_START) [OFApplication terminateWithStatus: 1]; @@ -201,161 +196,101 @@ [[TestsAppDelegate alloc] init]); #endif } @implementation TestsAppDelegate -- (void)outputString: (OFString *)str - inColor: (int)color -{ -#if defined(OF_PSP) - char space = ' '; - int y = pspDebugScreenGetY(); - - pspDebugScreenSetXY(0, y); - for (uint8_t i = 0; i < 68; i++) - pspDebugScreenPrintData(&space, 1); - - switch (color) { - case NO_COLOR: - pspDebugScreenSetTextColor(0xFFFFFF); - break; - case RED: - pspDebugScreenSetTextColor(0x0000FF); - break; - case GREEN: - pspDebugScreenSetTextColor(0x00FF00); - break; - case YELLOW: - pspDebugScreenSetTextColor(0x00FFFF); - break; - } - - pspDebugScreenSetXY(0, y); - pspDebugScreenPrintData(str.UTF8String, str.UTF8StringLength); -#elif defined(STDOUT) - switch (color) { - case NO_COLOR: - [of_stdout writeString: @"\r\033[K"]; -# if defined(OF_WII) || defined(OF_NINTENDO_DS) - [of_stdout writeString: @"\033[37m"]; -# endif - break; - case RED: - [of_stdout writeString: @"\r\033[K\033[31;1m"]; - break; - case GREEN: - [of_stdout writeString: @"\r\033[K\033[32;1m"]; - break; - case YELLOW: - [of_stdout writeString: @"\r\033[K\033[33;1m"]; - break; - } - - [of_stdout writeString: str]; - [of_stdout writeString: @"\033[m"]; -#elif defined(STDOUT_SIMPLE) - [of_stdout writeString: str]; -#else -# error No output method! -#endif -} - - (void)outputTesting: (OFString *)test inModule: (OFString *)module { - void *pool = objc_autoreleasePoolPush(); -#ifndef STDOUT_SIMPLE - [self outputString: [OFString stringWithFormat: @"[%@] %@: testing...", - module, test] - inColor: YELLOW]; -#else - [self outputString: [OFString stringWithFormat: @"[%@] %@: ", - module, test] - inColor: YELLOW]; -#endif - objc_autoreleasePoolPop(pool); + if (of_stdout.hasTerminal) { + of_stdout.foregroundColor = [OFColor yellow]; + [of_stdout writeFormat: @"[%@] %@: testing...", module, test]; + } else + [of_stdout writeFormat: @"[%@] %@: ", module, test]; } - (void)outputSuccess: (OFString *)test inModule: (OFString *)module { -#ifndef STDOUT_SIMPLE - void *pool = objc_autoreleasePoolPush(); - [self outputString: [OFString stringWithFormat: @"[%@] %@: ok\n", - module, test] - inColor: GREEN]; - objc_autoreleasePoolPop(pool); -#else - [self outputString: @"ok\n" - inColor: GREEN]; -#endif + if (of_stdout.hasTerminal) { + of_stdout.cursorColumn = 0; + of_stdout.foregroundColor = [OFColor lime]; + [of_stdout eraseLine]; + [of_stdout writeFormat: @"[%@] %@: ok\n", module, test]; + } else + [of_stdout writeLine: @"ok"]; } - (void)outputFailure: (OFString *)test inModule: (OFString *)module { -#ifndef STDOUT_SIMPLE - void *pool = objc_autoreleasePoolPush(); - [self outputString: [OFString stringWithFormat: @"[%@] %@: failed\n", - module, test] - inColor: RED]; - objc_autoreleasePoolPop(pool); - -# ifdef OF_WII - [self outputString: @"Press A to continue!\n" - inColor: NO_COLOR]; - for (;;) { - WPAD_ScanPads(); - - if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) - return; - - VIDEO_WaitVSync(); - } -# endif -# ifdef OF_PSP - [self outputString: @"Press X to continue!\n" - inColor: NO_COLOR]; - for (;;) { - SceCtrlData pad; - - sceCtrlReadBufferPositive(&pad, 1); - if (pad.Buttons & PSP_CTRL_CROSS) { - for (;;) { - sceCtrlReadBufferPositive(&pad, 1); - if (!(pad.Buttons & PSP_CTRL_CROSS)) - return; - } - } - } -# endif -# ifdef OF_NINTENDO_DS - [self outputString: @"Press A to continue!" - inColor: NO_COLOR]; - for (;;) { - swiWaitForVBlank(); - scanKeys(); - if (keysDown() & KEY_A) - break; - } -# endif -# ifdef OF_NINTENDO_3DS - [self outputString: @"Press A to continue!" - inColor: NO_COLOR]; - for (;;) { - hidScanInput(); - - if (hidKeysDown() & KEY_A) - break; - - gspWaitForVBlank(); - } -# endif -#else - [self outputString: @"failed\n" - inColor: RED]; -#endif + if (of_stdout.hasTerminal) { + of_stdout.cursorColumn = 0; + of_stdout.foregroundColor = [OFColor red]; + [of_stdout eraseLine]; + [of_stdout writeFormat: @"[%@] %@: failed\n", module, test]; + +#ifdef OF_WII + [of_stdout reset]; + [of_stdout writeLine: @"Press A to continue!"]; + + for (;;) { + WPAD_ScanPads(); + + if (WPAD_ButtonsDown(0) & WPAD_BUTTON_A) + return; + + VIDEO_WaitVSync(); + } +#endif +#ifdef OF_PSP + [of_stdout reset]; + [of_stdout writeLine: @"Press X to continue!"]; + + for (;;) { + SceCtrlData pad; + + sceCtrlReadBufferPositive(&pad, 1); + if (pad.Buttons & PSP_CTRL_CROSS) { + for (;;) { + sceCtrlReadBufferPositive(&pad, 1); + if (!(pad.Buttons & PSP_CTRL_CROSS)) + return; + } + } + } +#endif +#ifdef OF_NINTENDO_DS + [of_stdout reset]; + [of_stdout writeString: @"Press A to continue!"]; + + for (;;) { + swiWaitForVBlank(); + scanKeys(); + if (keysDown() & KEY_A) + break; + } +#endif +#ifdef OF_NINTENDO_3DS + [of_stdout reset]; + [of_stdout writeString: @"Press A to continue!"]; + + for (;;) { + hidScanInput(); + + if (hidKeysDown() & KEY_A) + break; + + gspWaitForVBlank(); + } +#endif + + of_stdout.cursorColumn = 0; + [of_stdout reset]; + [of_stdout eraseLine]; + } else + [of_stdout writeLine: @"failed"]; } - (void)applicationDidFinishLaunching { #if defined(OF_IOS) && defined(OF_HAVE_FILES) @@ -413,10 +348,18 @@ #endif #ifdef OF_HAVE_SOCKETS [self socketTests]; [self TCPSocketTests]; [self UDPSocketTests]; +# ifdef OF_HAVE_SCTP + [self SCTPSocketTests]; +# endif +# ifdef OF_HAVE_IPX + [self IPXSocketTests]; + [self SPXSocketTests]; + [self SPXStreamSocketTests]; +# endif [self kernelEventObserverTests]; #endif #ifdef OF_HAVE_THREADS [self threadTests]; #endif @@ -447,43 +390,42 @@ [self DNSResolverTests]; #endif [self systemInfoTests]; [self localeTests]; + [of_stdout reset]; + #if defined(OF_IOS) - [self outputString: [OFString stringWithFormat: @"%d tests failed!", - _fails] - inColor: NO_COLOR]; + [of_stdout writeFormat: @"%d tests failed!", _fails]; [OFApplication terminateWithStatus: _fails]; #elif defined(OF_WII) - [self outputString: @"Press home button to exit!\n" - inColor: NO_COLOR]; + [of_stdout writeString: @"Press home button to exit!"]; + for (;;) { WPAD_ScanPads(); if (WPAD_ButtonsDown(0) & WPAD_BUTTON_HOME) [OFApplication terminateWithStatus: _fails]; VIDEO_WaitVSync(); } #elif defined(OF_PSP) - [self outputString: [OFString stringWithFormat: @"%d tests failed!", - _fails] - inColor: NO_COLOR]; + [of_stdout writeFormat: @"%d tests failed!", _fails]; + sceKernelSleepThreadCB(); #elif defined(OF_NINTENDO_DS) - [self outputString: @"Press start button to exit!" - inColor: NO_COLOR]; + [of_stdout writeString: @"Press start button to exit!"]; + for (;;) { swiWaitForVBlank(); scanKeys(); if (keysDown() & KEY_START) [OFApplication terminateWithStatus: _fails]; } #elif defined(OF_NINTENDO_3DS) - [self outputString: @"Press start button to exit!" - inColor: NO_COLOR]; + [of_stdout writeString: @"Press start button to exit!"]; + for (;;) { hidScanInput(); if (hidKeysDown() & KEY_START) [OFApplication terminateWithStatus: _fails]; Index: tests/objc_sync/Makefile ================================================================== --- tests/objc_sync/Makefile +++ tests/objc_sync/Makefile @@ -9,24 +9,25 @@ .PHONY: run run: rm -f libobjfw.so.${OBJFW_LIB_MAJOR} rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} - rm -f libobjfw.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib + rm -f objfw.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR} rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} - rm -f libobjfwrt.dll libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib + rm -f objfwrt.dll libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib + rm -f ${OBJFWRT_AMIGA_LIB} if test -f ../../src/libobjfw.so; then \ ${LN_S} ../../src/libobjfw.so libobjfw.so.${OBJFW_LIB_MAJOR}; \ ${LN_S} ../../src/libobjfw.so \ libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ elif test -f ../../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; then \ ${LN_S} ../../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} \ libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ fi - if test -f ../../src/libobjfw.dll; then \ - ${LN_S} ../../src/libobjfw.dll libobjfw.dll; \ + if test -f ../../src/objfw.dll; then \ + ${LN_S} ../../src/objfw.dll objfw.dll; \ fi if test -f ../../src/libobjfw.dylib; then \ ${LN_S} ../../src/libobjfw.dylib \ libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ fi @@ -36,27 +37,31 @@ ${LN_S} ../../src/runtime/libobjfwrt.so \ libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ elif test -f ../../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; then \ ${LN_S} ../../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ fi - if test -f ../../src/runtime/libobjfwrt.dll; then \ - ${LN_S} ../../src/runtime/libobjfwrt.dll libobjfwrt.dll; \ + if test -f ../../src/runtime/objfwrt.dll; then \ + ${LN_S} ../../src/runtime/objfwrt.dll objfwrt.dll; \ fi if test -f ../../src/runtime/libobjfwrt.dylib; then \ ${LN_S} ../../src/runtime/libobjfwrt.dylib \ libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ + fi + if test -f ../../src/runtime/${OBJFWRT_AMIGA_LIB}; then \ + ${LN_S} ../../src/runtime/${OBJFWRT_AMIGA_LIB} \ + ${OBJFWRT_AMIGA_LIB}; \ fi LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH+:}$$LD_LIBRARY_PATH \ DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ LIBRARY_PATH=.$${LIBRARY_PATH+:}$$LIBRARY_PATH \ ${WRAPPER} ./${PROG_NOINST}; EXIT=$$?; \ rm -f libobjfw.so.${OBJFW_LIB_MAJOR}; \ - rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} libobjfw.dll; \ + rm -f objfw.so.${OBJFW_LIB_MAJOR_MINOR} objfw.dll; \ rm -f libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR}; \ - rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} libobjfwrt.dll; \ + rm -f objfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} objfwrt.dll; \ rm -f libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ exit $$EXIT -CPPFLAGS += -I../../src/runtime -I../../src -I../.. -LIBS := -L../../src -lobjfw ${LIBS} +CPPFLAGS += -I../../src -I../../src/runtime -I../.. +LIBS := -L../../src -lobjfw -L../../src/runtime ${RUNTIME_LIBS} ${LIBS} LD = ${OBJC} Index: tests/plugin/TestPlugin.m ================================================================== --- tests/plugin/TestPlugin.m +++ tests/plugin/TestPlugin.m @@ -22,11 +22,22 @@ #ifdef OF_OBJFW_RUNTIME # import "runtime/private.h" OF_DESTRUCTOR() { - objc_unregister_class(objc_getClass("TestPlugin")); + Class class = objc_getClass("TestPlugin"); + + if (class == Nil) + /* + * musl has broken dlclose(): Instead of calling the destructor + * on dlclose(), they call it on exit(). This of course means + * that our tests might have already called objc_exit() and the + * class is already gone. + */ + return; + + objc_unregister_class(class); } #endif @implementation TestPlugin - (int)test: (int)num ADDED tests/terminal/Makefile Index: tests/terminal/Makefile ================================================================== --- tests/terminal/Makefile +++ tests/terminal/Makefile @@ -0,0 +1,67 @@ +include ../../extra.mk + +PROG_NOINST = terminal_tests${PROG_SUFFIX} +SRCS = TerminalTests.m + +include ../../buildsys.mk + +post-all: ${RUN_TESTS} + +.PHONY: run +run: + rm -f libobjfw.so.${OBJFW_LIB_MAJOR} + rm -f libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} + rm -f objfw.dll libobjfw.${OBJFW_LIB_MAJOR}.dylib + rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR} + rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} + rm -f objfwrt.dll libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib + rm -f ${OBJFWRT_AMIGA_LIB} + if test -f ../../src/libobjfw.so; then \ + ${LN_S} ../../src/libobjfw.so libobjfw.so.${OBJFW_LIB_MAJOR}; \ + ${LN_S} ../../src/libobjfw.so \ + libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ + elif test -f ../../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; then \ + ${LN_S} ../../src/libobjfw.so.${OBJFW_LIB_MAJOR_MINOR} \ + libobjfw.so.${OBJFW_LIB_MAJOR_MINOR}; \ + fi + if test -f ../../src/objfw.dll; then \ + ${LN_S} ../../src/objfw.dll objfw.dll; \ + fi + if test -f ../../src/libobjfw.dylib; then \ + ${LN_S} ../../src/libobjfw.dylib \ + libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ + fi + if test -f ../../src/runtime/libobjfwrt.so; then \ + ${LN_S} ../../src/runtime/libobjfwrt.so \ + libobjfwrt.so.${OBJFWRT_LIB_MAJOR}; \ + ${LN_S} ../../src/runtime/libobjfwrt.so \ + libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ + elif test -f ../../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; then \ + ${LN_S} ../../src/runtime/libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} libobjfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR}; \ + fi + if test -f ../../src/runtime/objfwrt.dll; then \ + ${LN_S} ../../src/runtime/objfwrt.dll objfwrt.dll; \ + fi + if test -f ../../src/runtime/libobjfwrt.dylib; then \ + ${LN_S} ../../src/runtime/libobjfwrt.dylib \ + libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ + fi + if test -f ../../src/runtime/${OBJFWRT_AMIGA_LIB}; then \ + ${LN_S} ../../src/runtime/${OBJFWRT_AMIGA_LIB} \ + ${OBJFWRT_AMIGA_LIB}; \ + fi + LD_LIBRARY_PATH=.$${LD_LIBRARY_PATH+:}$$LD_LIBRARY_PATH \ + DYLD_LIBRARY_PATH=.$${DYLD_LIBRARY_PATH+:}$$DYLD_LIBRARY_PATH \ + LIBRARY_PATH=.$${LIBRARY_PATH+:}$$LIBRARY_PATH \ + ${WRAPPER} ./${PROG_NOINST}; EXIT=$$?; \ + rm -f libobjfw.so.${OBJFW_LIB_MAJOR}; \ + rm -f objfw.so.${OBJFW_LIB_MAJOR_MINOR} objfw.dll; \ + rm -f libobjfw.${OBJFW_LIB_MAJOR}.dylib; \ + rm -f libobjfwrt.so.${OBJFWRT_LIB_MAJOR}; \ + rm -f objfwrt.so.${OBJFWRT_LIB_MAJOR_MINOR} objfwrt.dll; \ + rm -f libobjfwrt.${OBJFWRT_LIB_MAJOR}.dylib; \ + exit $$EXIT + +CPPFLAGS += -I../../src -I../../src/exceptions -I../../src/runtime -I../.. +LIBS := -L../../src -lobjfw -L../../src/runtime ${RUNTIME_LIBS} ${LIBS} +LD = ${OBJC} ADDED tests/terminal/TerminalTests.m Index: tests/terminal/TerminalTests.m ================================================================== --- tests/terminal/TerminalTests.m +++ tests/terminal/TerminalTests.m @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFApplication.h" +#import "OFArray.h" +#import "OFColor.h" +#import "OFStdIOStream.h" +#import "OFThread.h" + +@interface TerminalTests: OFObject +@end + +OF_APPLICATION_DELEGATE(TerminalTests) + +@implementation TerminalTests +- (void)applicationDidFinishLaunching +{ + OFArray *colors = [OFArray arrayWithObjects: + [OFColor black], [OFColor silver], [OFColor grey], [OFColor white], + [OFColor maroon], [OFColor red], [OFColor purple], + [OFColor fuchsia], [OFColor green], [OFColor lime], [OFColor olive], + [OFColor yellow], [OFColor navy], [OFColor blue], [OFColor teal], + [OFColor aqua], nil]; + size_t i; + OFEnumerator OF_GENERIC(OFColor *) *reverseEnumerator; + + [of_stdout writeFormat: @"%dx%d\n", of_stdout.columns, of_stdout.rows]; + + i = 0; + for (OFColor *color in colors) { + of_stdout.foregroundColor = color; + [of_stdout writeFormat: @"%zx", i++]; + } + [of_stdout reset]; + [of_stdout writeLine: @"R"]; + + i = 0; + for (OFColor *color in colors) { + of_stdout.backgroundColor = color; + [of_stdout writeFormat: @"%zx", i++]; + } + [of_stdout reset]; + [of_stdout writeLine: @"R"]; + + i = 0; + reverseEnumerator = [colors.reversedArray objectEnumerator]; + for (OFColor *color in colors) { + of_stdout.foregroundColor = color; + of_stdout.backgroundColor = [reverseEnumerator nextObject]; + [of_stdout writeFormat: @"%zx", i++]; + } + [of_stdout reset]; + [of_stdout writeLine: @"R"]; + + for (i = 0; i < colors.count * 2; i++) { + if (i % 2) + of_stdout.backgroundColor = [colors objectAtIndex: + ((i / 2) + 2) % colors.count]; + else + of_stdout.foregroundColor = + [colors objectAtIndex: i / 2]; + + [of_stdout writeFormat: @"%zx", i / 2]; + } + [of_stdout reset]; + [of_stdout writeLine: @"R"]; + + [of_stdout writeLine: @"Press return"]; + [of_stdin readLine]; + + of_stdout.backgroundColor = [OFColor green]; + [of_stdout writeString: @"Hello!"]; + [OFThread sleepForTimeInterval: 2]; + [of_stdout eraseLine]; + [of_stdout writeString: @"World!"]; + [OFThread sleepForTimeInterval: 2]; + + [of_stdout clear]; + [OFThread sleepForTimeInterval: 2]; + + of_stdout.cursorPosition = of_point(5, 3); + [of_stdout writeString: @"Text at (5, 3)"]; + [OFThread sleepForTimeInterval: 2]; + + [of_stdout setRelativeCursorPosition: of_point(-2, 0)]; + [OFThread sleepForTimeInterval: 2]; + [of_stdout setRelativeCursorPosition: of_point(2, 0)]; + [OFThread sleepForTimeInterval: 2]; + [of_stdout setRelativeCursorPosition: of_point(0, -2)]; + [OFThread sleepForTimeInterval: 2]; + [of_stdout setRelativeCursorPosition: of_point(0, 2)]; + [OFThread sleepForTimeInterval: 2]; + [of_stdout setRelativeCursorPosition: of_point(1, 1)]; + [OFThread sleepForTimeInterval: 2]; + [of_stdout setRelativeCursorPosition: of_point(-1, -1)]; + [OFThread sleepForTimeInterval: 2]; + + of_stdout.cursorColumn = 2; + [OFThread sleepForTimeInterval: 2]; + + [of_stdout reset]; + + [OFApplication terminate]; +} +@end Index: utils/Makefile ================================================================== --- utils/Makefile +++ utils/Makefile @@ -2,10 +2,11 @@ SUBDIRS += ${OFARC} \ ${OFDNS} \ ${OFHASH} \ ${OFHTTP} \ + ${OFSOCK} \ completions include ../buildsys.mk DISTCLEAN = objfw-config Index: utils/completions/fish/Makefile ================================================================== --- utils/completions/fish/Makefile +++ utils/completions/fish/Makefile @@ -1,9 +1,10 @@ DATA = objfw-compile.fish \ objfw-config.fish \ ofarc.fish \ + ofdns.fish \ ofhash.fish \ ofhttp.fish include ../../../buildsys.mk PACKAGE_NAME = fish/vendor_completions.d ADDED utils/completions/fish/ofdns.fish Index: utils/completions/fish/ofdns.fish ================================================================== --- utils/completions/fish/ofdns.fish +++ utils/completions/fish/ofdns.fish @@ -0,0 +1,5 @@ +complete -c ofdns -s c -l class -x -d 'The DNS class to query (defaults to IN)' +complete -c ofdns -s h -l help -d 'Show help' +complete -c ofdns -s s -l server -x -d 'The server to query' +complete -c ofdns -s t -l type -x \ + -d 'The record type to query (defaults to ALL, can be repeated)' Index: utils/completions/fish/ofhttp.fish ================================================================== --- utils/completions/fish/ofhttp.fish +++ utils/completions/fish/ofhttp.fish @@ -1,5 +1,6 @@ +complete -c ofhttp -x complete -c ofhttp -s b -l body -r -d 'Specify the file to send as body' complete -c ofhttp -s c -l continue -d 'Continue download of existing file' complete -c ofhttp -s f -l force -d 'Force / overwrite existing file' complete -c ofhttp -s h -l help -d 'Show help' complete -c ofhttp -s H -l header -x -d 'Add a header (e.g. X-Foo:Bar)' Index: utils/ofarc/LHAArchive.m ================================================================== --- utils/ofarc/LHAArchive.m +++ utils/ofarc/LHAArchive.m @@ -119,16 +119,28 @@ @"%04" PRIX16, entry.CRC16]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_compressed_size", - @"Compressed: %[size] bytes", + [@"[" + @" 'Compressed: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]" JSONValue], @"size", compressedSize)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_uncompressed_size", - @"Uncompressed: %[size] bytes", + [@"[" + @" 'Uncompressed: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]" JSONValue], @"size", uncompressedSize)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_compression_method", @"Compression method: %[method]", Index: utils/ofarc/OFArc.m ================================================================== --- utils/ofarc/OFArc.m +++ utils/ofarc/OFArc.m @@ -35,11 +35,10 @@ #import "TarArchive.h" #import "ZIPArchive.h" #import "OFCreateDirectoryFailedException.h" #import "OFInvalidArgumentException.h" -#import "OFInvalidEncodingException.h" #import "OFInvalidFormatException.h" #import "OFNotImplementedException.h" #import "OFOpenItemFailedException.h" #import "OFReadFailedException.h" #import "OFSeekFailedException.h" @@ -149,11 +148,11 @@ } @implementation OFArc - (void)applicationDidFinishLaunching { - OFString *outputDir = nil, *encodingString = nil, *type = nil; + OFString *outputDir, *encodingString, *type; const of_options_parser_option_t options[] = { { 'a', @"append", 0, NULL, NULL }, { 'c', @"create", 0, NULL, NULL }, { 'C', @"directory", 1, NULL, &outputDir }, { 'E', @"encoding", 1, NULL, &encodingString }, @@ -166,13 +165,13 @@ { 't', @"type", 1, NULL, &type }, { 'v', @"verbose", 0, NULL, NULL }, { 'x', @"extract", 0, NULL, NULL }, { '\0', nil, 0, NULL, NULL } }; - OFOptionsParser *optionsParser; of_unichar_t option, mode = '\0'; of_string_encoding_t encoding = OF_STRING_ENCODING_AUTODETECT; + OFOptionsParser *optionsParser; OFArray OF_GENERIC(OFString *) *remainingArguments, *files; id archive; #ifdef OF_HAVE_SANDBOX OFSandbox *sandbox = [OFSandbox sandbox]; @@ -291,17 +290,18 @@ @"prog", [OFApplication programName], @"opt", optStr)]; } [OFApplication terminateWithStatus: 1]; + break; } } @try { if (encodingString != nil) encoding = of_string_parse_encoding(encodingString); - } @catch (OFInvalidEncodingException *e) { + } @catch (OFInvalidArgumentException *e) { [of_stderr writeLine: OF_LOCALIZED( @"invalid_encoding", @"%[prog]: Invalid encoding: %[encoding]", @"prog", [OFApplication programName], @"encoding", encodingString)]; @@ -671,11 +671,12 @@ if ([line isEqual: @"n"] || [line isEqual: @"N"]) { if (_outputLevel >= 0) [of_stdout writeLine: OF_LOCALIZED(@"skipping_file", @"Skipping %[file]...", @"file", fileName)]; - return false; + + return false; } if (_outputLevel >= 0) [of_stdout writeString: OF_LOCALIZED(@"extracting_file", @"Extracting %[file]...", Index: utils/ofarc/TarArchive.m ================================================================== --- utils/ofarc/TarArchive.m +++ utils/ofarc/TarArchive.m @@ -108,11 +108,17 @@ OFString *GID = [OFString stringWithFormat: @"%u", entry.GID]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED(@"list_size", - @"Size: %[size] bytes", + [@"[" + @" 'Size: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]" JSONValue], @"size", size)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED(@"list_mode", @"Mode: %[mode]", @"mode", mode)]; Index: utils/ofarc/ZIPArchive.m ================================================================== --- utils/ofarc/ZIPArchive.m +++ utils/ofarc/ZIPArchive.m @@ -120,16 +120,28 @@ localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_compressed_size", - @"Compressed: %[size] bytes", + [@"[" + @" 'Compressed: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]" JSONValue], @"size", compressedSize)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_uncompressed_size", - @"Uncompressed: %[size] bytes", + [@"[" + @" 'Uncompressed: '," + @" [" + @" {'size == 1': '1 byte'}," + @" {'': '%[size] bytes'}" + @" ]" + @"]" JSONValue], @"size", uncompressedSize)]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( @"list_compression_method", @"Compression method: %[method]", Index: utils/ofarc/lang/de.json ================================================================== --- utils/ofarc/lang/de.json +++ utils/ofarc/lang/de.json @@ -66,11 +66,17 @@ "Kann keine spezifische Datei aus einem .gz-Archiv entpacken!" ], "cannot_print_specific_file_from_gz": [ "Kann keine spezifische Datei aus einem .gz-Archiv ausgeben!" ], - "list_size": "Größe: %[size] Bytes", + "list_size": [ + "Größe: ", + [ + {"size == 1": "1 Byte"}, + {"": "%[size] Bytes"} + ] + ], "list_mode": "Modus: %[mode]", "list_owner": "Besitzer: %[owner]", "list_group": "Gruppe: %[group]", "list_header_level": "Header-Level: %[level]", "list_modification_date": "Änderungsdatum: %[date]", @@ -84,12 +90,24 @@ "list_device_minor": "Minor-Nummer des Geräts: %[minor]", "list_type_directory": "Typ: Verzeichnis", "list_type_fifo": "Typ: FIFO", "list_type_contiguous_file": "Typ: Zusammenhängende Datei", "list_type_unknown": "Typ: Unbekannt", - "list_compressed_size": "Komprimierte Größe: %[size] Bytes", - "list_uncompressed_size": "Unkomprimierte Größe: %[size] Bytes", + "list_compressed_size": [ + "Komprimierte Größe: ", + [ + {"size == 1": "1 Byte"}, + {"": "%[size] Bytes"} + ] + ], + "list_uncompressed_size": [ + "Unkomprimierte Größe: ", + [ + {"size == 1": "1 Byte"}, + {"": "%[size] Bytes"} + ] + ], "list_compression_method": "Kompressionsmethode: %[method]", "list_date": "Datum: %[date]", "list_osid": "Betriebssystem-Identifikator: %[osid]", "list_extensions": "Erweiterungen: %[extensions]", "list_version_made_by": "Erstellt mit Version: %[version]", Index: utils/ofdns/Makefile ================================================================== --- utils/ofdns/Makefile +++ utils/ofdns/Makefile @@ -1,9 +1,11 @@ include ../../extra.mk PROG = ofdns${PROG_SUFFIX} SRCS = OFDNS.m +DATA = lang/de.json \ + lang/languages.json include ../../buildsys.mk PACKAGE_NAME = ofdns @@ -10,11 +12,12 @@ ${PROG}: ${LIBOBJFW_DEP_LVL2} ${LIBOBJFWRT_DEP_LVL2} CPPFLAGS += -I../../src \ -I../../src/runtime \ -I../../src/exceptions \ - -I../.. + -I../.. \ + -DLANGUAGE_DIR=\"${datadir}/ofdns/lang\" LIBS := -L../../src -lobjfw \ -L../../src/runtime -L../../src/runtime/linklib ${RUNTIME_LIBS} \ ${LIBS} LD = ${OBJC} LDFLAGS += ${LDFLAGS_RPATH} Index: utils/ofdns/OFDNS.m ================================================================== --- utils/ofdns/OFDNS.m +++ utils/ofdns/OFDNS.m @@ -18,41 +18,96 @@ #include "config.h" #import "OFApplication.h" #import "OFArray.h" #import "OFDNSResolver.h" +#import "OFLocale.h" +#import "OFOptionsParser.h" #import "OFSandbox.h" #import "OFStdIOStream.h" @interface OFDNS: OFObject +{ + size_t _inFlight; + int _errors; +} @end OF_APPLICATION_DELEGATE(OFDNS) + +static void +help(OFStream *stream, bool full, int status) +{ + [of_stderr writeLine: + OF_LOCALIZED(@"usage", + @"Usage: %[prog] -[chst] domain1 [domain2 ...]", + @"prog", [OFApplication programName])]; + + if (full) { + [stream writeString: @"\n"]; + [stream writeLine: OF_LOCALIZED(@"full_usage", + @"Options:\n " + @"-c --class " + @" The DNS class to query (defaults to IN)\n " + @"-h --help " + @" Show this help\n " + @"-s --server" + @" The server to query\n " + @"-t --type " + @" The record type to query (defaults to ALL, can be " + @"repeated)")]; + } + + [OFApplication terminateWithStatus: status]; +} @implementation OFDNS - (void)resolver: (OFDNSResolver *)resolver didPerformQuery: (OFDNSQuery *)query response: (OFDNSResponse *)response exception: (id)exception { - if (exception != nil) { - [of_stderr writeFormat: @"Failed to resolve: %@\n", exception]; - [OFApplication terminateWithStatus: 1]; + _inFlight--; + + if (exception == nil) + [of_stdout writeFormat: @"%@\n", response]; + else { + [of_stderr writeLine: OF_LOCALIZED( + @"failed_to_resolve", + @"Failed to resolve: %[exception]", + @"exception", exception)]; + _errors++; } - [of_stdout writeFormat: @"%@\n", response]; - - [OFApplication terminate]; + if (_inFlight == 0) + [OFApplication terminateWithStatus: _errors]; } - (void)applicationDidFinishLaunching { - OFArray OF_GENERIC(OFString *) *arguments = [OFApplication arguments]; - of_dns_class_t DNSClass = OF_DNS_CLASS_ANY; - of_dns_record_type_t recordType = OF_DNS_RECORD_TYPE_ALL; - OFDNSQuery *query; + OFString *DNSClassString, *server; + const of_options_parser_option_t options[] = { + { 'c', @"class", 1, NULL, &DNSClassString }, + { 'h', @"help", 0, NULL, NULL }, + { 's', @"server", 1, NULL, &server }, + { 't', @"type", 1, NULL, NULL }, + { '\0', nil, 0, NULL, NULL } + }; + OFMutableArray OF_GENERIC(OFString *) *recordTypes; + OFOptionsParser *optionsParser; + of_unichar_t option; + OFArray OF_GENERIC(OFString *) *remainingArguments; OFDNSResolver *resolver; + of_dns_class_t DNSClass; + +#ifdef OF_HAVE_FILES +# ifndef OF_AMIGAOS + [OFLocale addLanguageDirectory: @LANGUAGE_DIR]; +# else + [OFLocale addLanguageDirectory: @"PROGDIR:/share/ofdns/lang"]; +# endif +#endif #ifdef OF_HAVE_SANDBOX OFSandbox *sandbox = [[OFSandbox alloc] init]; @try { sandbox.allowsStdIO = true; @@ -62,34 +117,95 @@ } @finally { [sandbox release]; } #endif - if (arguments.count < 1 || arguments.count > 4) { - [of_stderr writeFormat: - @"Usage: %@ host [type [class [server]]]\n", - [OFApplication programName]]; - [OFApplication terminateWithStatus: 1]; + recordTypes = [OFMutableArray array]; + + optionsParser = [OFOptionsParser parserWithOptions: options]; + while ((option = [optionsParser nextOption]) != '\0') { + switch (option) { + case 't': + [recordTypes addObject: optionsParser.argument]; + break; + case 'h': + help(of_stdout, true, 0); + break; + case ':': + if (optionsParser.lastLongOption != nil) + [of_stderr writeLine: OF_LOCALIZED( + @"long_option_required_argument", + @"%[prog]: Option --%[opt] requires an " + @"argument", + @"prog", [OFApplication programName], + @"opt", optionsParser.lastLongOption)]; + else { + OFString *optStr = [OFString + stringWithFormat: @"%C", + optionsParser.lastOption]; + [of_stderr writeLine: OF_LOCALIZED( + @"option_requires_argument", + @"%[prog]: Option -%[opt] requires an " + @"argument", + @"prog", [OFApplication programName], + @"opt", optStr)]; + } + + [OFApplication terminateWithStatus: 1]; + break; + case '?': + if (optionsParser.lastLongOption != nil) + [of_stderr writeLine: OF_LOCALIZED( + @"unknown_long_option", + @"%[prog]: Unknown option: --%[opt]", + @"prog", [OFApplication programName], + @"opt", optionsParser.lastLongOption)]; + else { + OFString *optStr = [OFString + stringWithFormat: @"%C", + optionsParser.lastOption]; + [of_stderr writeLine: OF_LOCALIZED( + @"Unknown_option", + @"%[prog]: Unknown option: -%[opt]", + @"prog", [OFApplication programName], + @"opt", optStr)]; + } + + [OFApplication terminateWithStatus: 1]; + break; + } } + + remainingArguments = optionsParser.remainingArguments; + + if (remainingArguments.count < 1) + help(of_stderr, false, 1); resolver = [OFDNSResolver resolver]; - - if (arguments.count >= 2) - recordType = of_dns_record_type_parse( - [arguments objectAtIndex: 1]); - - if (arguments.count >= 3) - DNSClass = of_dns_class_parse([arguments objectAtIndex: 2]); - - if (arguments.count >= 4) { - resolver.configReloadInterval = 0; - resolver.nameServers = - [arguments objectsInRange: of_range(3, 1)]; - } - - query = [OFDNSQuery queryWithDomainName: [arguments objectAtIndex: 0] - DNSClass: DNSClass - recordType: recordType]; - [resolver asyncPerformQuery: query - delegate: self]; + DNSClass = (DNSClassString != nil + ? of_dns_class_parse(DNSClassString) + : OF_DNS_CLASS_IN); + + if (recordTypes.count == 0) + [recordTypes addObject: @"ALL"]; + + if (server != nil) { + resolver.configReloadInterval = 0; + resolver.nameServers = [OFArray arrayWithObject: server]; + } + + for (OFString *domainName in remainingArguments) { + for (OFString *recordTypeString in recordTypes) { + of_dns_record_type_t recordType = + of_dns_record_type_parse(recordTypeString); + OFDNSQuery *query = + [OFDNSQuery queryWithDomainName: domainName + DNSClass: DNSClass + recordType: recordType]; + + _inFlight++; + [resolver asyncPerformQuery: query + delegate: self]; + } + } } @end ADDED utils/ofdns/lang/de.json Index: utils/ofdns/lang/de.json ================================================================== --- utils/ofdns/lang/de.json +++ utils/ofdns/lang/de.json @@ -0,0 +1,18 @@ +{ + "usage": "Benutzung: %[prog] -[chst] domain1 [domain2 ...]", + "full_usage": [ + "Optionen:\n", + " -c --class Die anzufragende DNS-Klasse (standardmäßig IN)\n", + " -h --help Diese Hilfe anzeigen\n", + " -s --server Der abzufragende Server\n", + " -t --type Der anzufragende Record-Typ (standardmäßig ALL,\n", + " kann wiederholt werden)" + ], + "long_option_requires_argument": [ + "%[prog]: Option --%[opt] benötigt ein Argument" + ], + "option_requires_argument": "%[prog]: Option -%[opt] benötigt ein Argument", + "unknown_long_option": "%[prog]: Unbekannte Option: --%[opt]", + "unknown_option": "%[prog]: Unbekannte Option: -%[opt]", + "failed_to_resolve": "Auflösen fehlgeschlagen: %[exception]" +} ADDED utils/ofdns/lang/languages.json Index: utils/ofdns/lang/languages.json ================================================================== --- utils/ofdns/lang/languages.json +++ utils/ofdns/lang/languages.json @@ -0,0 +1,11 @@ +{ + "de": { + "": "de" + }, + "deutsch": { + "": "de" + }, + "german": { + "": "de" + } +} Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -35,10 +35,11 @@ #import "OFTLSSocket.h" #import "OFURL.h" #import "OFConnectionFailedException.h" #import "OFHTTPRequestFailedException.h" +#import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFInvalidServerReplyException.h" #import "OFOpenItemFailedException.h" #import "OFOutOfRangeException.h" #import "OFReadFailedException.h" @@ -89,10 +90,12 @@ [stream writeString: @"\n"]; [stream writeLine: OF_LOCALIZED(@"full_usage", @"Options:\n " @"-b --body " @" Specify the file to send as body\n " + @" " + @" (- for standard input)\n " @"-c --continue " @" Continue download of existing file\n " @"-f --force " @" Force / overwrite existing file\n " @"-h --help " @@ -317,41 +320,47 @@ forKey: name]; } - (void)setBody: (OFString *)path { - uintmax_t bodySize; + OFString *contentLength = nil; [_body release]; - _body = [[OFFile alloc] initWithPath: path - mode: @"r"]; + _body = nil; - bodySize = [[OFFileManager defaultManager] attributesOfItemAtPath: path] - .fileSize; - [_clientHeaders setObject: [OFString stringWithFormat: @"%ju", bodySize] - forKey: @"Content-Length"]; + if ([path isEqual: @"-"]) + _body = [of_stdin copy]; + else { + _body = [[OFFile alloc] initWithPath: path + mode: @"r"]; + + @try { + uintmax_t fileSize = [[OFFileManager defaultManager] + attributesOfItemAtPath: path].fileSize; + + contentLength = + [OFString stringWithFormat: @"%ju", fileSize]; + [_clientHeaders setObject: contentLength + forKey: @"Content-Length"]; + } @catch (OFRetrieveItemAttributesFailedException *e) { + } + } + + if (contentLength == nil) + [_clientHeaders setObject: @"chunked" + forKey: @"Transfer-Encoding"]; } - (void)setMethod: (OFString *)method { void *pool = objc_autoreleasePoolPush(); method = method.uppercaseString; - if ([method isEqual: @"GET"]) - _method = OF_HTTP_REQUEST_METHOD_GET; - else if ([method isEqual: @"HEAD"]) - _method = OF_HTTP_REQUEST_METHOD_HEAD; - else if ([method isEqual: @"POST"]) - _method = OF_HTTP_REQUEST_METHOD_POST; - else if ([method isEqual: @"PUT"]) - _method = OF_HTTP_REQUEST_METHOD_PUT; - else if ([method isEqual: @"DELETE"]) - _method = OF_HTTP_REQUEST_METHOD_DELETE; - else if ([method isEqual: @"TRACE"]) - _method = OF_HTTP_REQUEST_METHOD_TRACE; - else { + @try { + _method = of_http_request_method_from_string(method); + } @catch (OFInvalidArgumentException *e) { [of_stderr writeLine: OF_LOCALIZED(@"invalid_input_method", @"%[prog]: Invalid request method %[method]!", @"prog", [OFApplication programName], @"method", method)]; [OFApplication terminateWithStatus: 1]; @@ -547,11 +556,11 @@ @"prog", [OFApplication programName])]; [OFApplication terminateWithStatus: 1]; } if (_insecure) - _HTTPClient.insecureRedirectsAllowed = true; + _HTTPClient.allowsInsecureRedirects = true; [self performSelector: @selector(downloadNextURL) afterDelay: 0]; } @@ -558,12 +567,12 @@ - (void)client: (OFHTTPClient *)client didCreateSocket: (OFTCPSocket *)sock request: (OFHTTPRequest *)request { if (_insecure && [sock respondsToSelector: - @selector(setCertificateVerificationEnabled:)]) - ((id )sock).certificateVerificationEnabled = false; + @selector(setVerifiesCertificates:)]) + ((id )sock).verifiesCertificates = false; } - (void)client: (OFHTTPClient *)client wantsRequestBody: (OFStream *)body request: (OFHTTPRequest *)request @@ -680,14 +689,19 @@ @"prog", [OFApplication programName], @"url", request.URL.string, @"error", error, @"exception", e)]; } else if ([e isKindOfClass: [OFHTTPRequestFailedException class]]) { + short statusCode = [[e response] statusCode]; + OFString *codeString = [OFString stringWithFormat: @"%d %@", + statusCode, of_http_status_code_to_string(statusCode)]; [of_stderr writeLine: OF_LOCALIZED(@"download_failed", - @"%[prog]: Failed to download <%[url]>!", + @"%[prog]: Failed to download <%[url]>!\n" + @" HTTP status code: %[code]", @"prog", [OFApplication programName], - @"url", request.URL.string)]; + @"url", request.URL.string, + @"code", codeString)]; } else @throw e; _errorCode = 1; [self performSelector: @selector(downloadNextURL) @@ -711,11 +725,12 @@ [of_stdout writeString: @"\n Error!\n"]; URL = [_URLs objectAtIndex: _URLIndex - 1]; [of_stderr writeLine: OF_LOCALIZED( @"download_failed_exception", - @"%[prog]: Failed to download <%[url]>: %[exception]", + @"%[prog]: Failed to download <%[url]>!\n" + @" %[exception]", @"prog", [OFApplication programName], @"url", URL, @"exception", exception)]; _errorCode = 1; @@ -795,11 +810,16 @@ @"num", lengthString); } else { lengthString = [OFString stringWithFormat: @"%jd", _resumedFrom + _length]; lengthString = OF_LOCALIZED(@"size_bytes", - @"%[num] bytes", + [@"[" + @" [" + @" {'num == 1': '1 byte'}," + @" {'': '%[num] bytes'}" + @" ]" + @"]" JSONValue], @"num", lengthString); } } else lengthString = OF_LOCALIZED(@"size_unknown", @"unknown"); @@ -845,10 +865,13 @@ - (void)client: (OFHTTPClient *)client didPerformRequest: (OFHTTPRequest *)request response: (OFHTTPResponse *)response { + if (_method == OF_HTTP_REQUEST_METHOD_HEAD) + goto next; + if (_detectFileNameRequest) { _currentFileName = [fileNameFromContentDisposition( [response.headers objectForKey: @"Content-Disposition"]) copy]; _detectedFileName = true; Index: utils/ofhttp/ProgressBar.m ================================================================== --- utils/ofhttp/ProgressBar.m +++ utils/ofhttp/ProgressBar.m @@ -202,11 +202,16 @@ @"num", num)]; } else { OFString *num = [OFString stringWithFormat: @"%jd", _resumedFrom + _received]; [of_stdout writeString: OF_LOCALIZED(@"progress_bytes", - @"%[num] bytes", + [@"[" + @" [" + @" {'num == 1': '1 byte '}," + @" {'': '%[num] bytes'}" + @" ]" + @"]" JSONValue], @"num", num)]; } [of_stdout writeString: @" "]; Index: utils/ofhttp/lang/de.json ================================================================== --- utils/ofhttp/lang/de.json +++ utils/ofhttp/lang/de.json @@ -1,10 +1,11 @@ { "usage": "Benutzung: %[prog] -[cehHmoOPqv] url1 [url2 ...]", "full_usage": [ "Optionen:\n", " -b --body Angegebene Datei als Body übergeben\n", + " (- für Standard-Eingabe)\n", " -c --continue Download von existierender Datei ", "fortsetzen\n", " -f --force Existierende Datei überschreiben\n", " -h --help Diese Hilfe anzeigen\n", " -H --header Einen Header (z.B. X-Foo:Bar) hinzufügen\n", @@ -61,22 +62,31 @@ "download_failed_read_or_write_failed_write": "Schreiben", "download_failed_read_or_write_failed": [ "%[prog]: Fehler beim Download von <%[url]>!\n", " %[error]: %[exception]" ], - "download_failed": "%[prog]: Fehler beim Download von <%[url]>!", + "download_failed": [ + "%[prog]: Fehler beim Download von <%[url]>!\n", + " HTTP Status-Code: %[code]" + ], "download_failed_exception": [ - "%[prog]: Fehler beim Download von <%[url]>: %[exception]" + "%[prog]: Fehler beim Download von <%[url]>!\n", + " %[exception]" ], "download_done": "Fertig!", "invalid_url": "%[prog]: Ungültige URL: <%[url]>!", "invalid_scheme": "%[prog]: Ungültiges Schema: <%[url]>!", "type_unknown": "unbekannt", "size_gib": "%[num] GiB", "size_mib": "%[num] MiB", "size_kib": "%[num] KiB", - "size_bytes": "%[num] Bytes", + "size_bytes": [ + [ + {"num == 1": "1 Byte"}, + {"": "%[num] Bytes"} + ] + ], "size_unknown": "unbekannt", "info_name_unaligned": "Name: %[name]", "info_name": "Name: %[name]", "info_type": "Typ: %[type]", "info_size": "Größe: %[size]", @@ -83,7 +93,12 @@ "output_already_exists": "%[prog]: Datei %[filename] existiert bereits!", "failed_to_open_output": [ "%[prog]: Kann Datei %[filename] nicht öffnen: %[exception]" ], "eta_days": "%[num] t ", - "progress_bytes": "%[num] Bytes" + "progress_bytes": [ + [ + {"num == 1": "1 Byte "}, + {"": "%[num] Bytes"} + ] + ] } ADDED utils/ofsock/Makefile Index: utils/ofsock/Makefile ================================================================== --- utils/ofsock/Makefile +++ utils/ofsock/Makefile @@ -0,0 +1,20 @@ +include ../../extra.mk + +PROG = ofsock${PROG_SUFFIX} +SRCS = OFSock.m + +include ../../buildsys.mk + +PACKAGE_NAME = ofsock + +${PROG}: ${LIBOBJFW_DEP_LVL2} ${LIBOBJFWRT_DEP_LVL2} + +CPPFLAGS += -I../../src \ + -I../../src/runtime \ + -I../../src/exceptions \ + -I../.. +LIBS := -L../../src -lobjfw \ + -L../../src/runtime -L../../src/runtime/linklib ${RUNTIME_LIBS} \ + ${LIBS} +LD = ${OBJC} +LDFLAGS += ${LDFLAGS_RPATH} ADDED utils/ofsock/OFSock.m Index: utils/ofsock/OFSock.m ================================================================== --- utils/ofsock/OFSock.m +++ utils/ofsock/OFSock.m @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFApplication.h" +#import "OFArray.h" +#import "OFNumber.h" +#import "OFPair.h" +#import "OFStdIOStream.h" +#import "OFStream.h" +#import "OFString.h" +#import "OFTCPSocket.h" +#import "OFURL.h" + +#define BUFFER_LEN 4096 + +@interface OFSock: OFObject +{ + char _buffer[BUFFER_LEN]; + OFMutableArray OF_GENERIC(OFPair OF_GENERIC(OFStream *, OFStream *) *) + *_streams; + int _errors; +} +@end + +OF_APPLICATION_DELEGATE(OFSock) + +static OFPair OF_GENERIC(OFStream *, OFStream *) * +streamFromString(OFString *string) +{ + OFURL *URL; + OFString *scheme; + + if ([string isEqual: @"-"]) + return [OFPair pairWithFirstObject: of_stdin + secondObject: of_stdout]; + + URL = [OFURL URLWithString: string]; + scheme = URL.scheme; + + if ([scheme isEqual: @"tcp"]) { + OFTCPSocket *sock = [OFTCPSocket socket]; + + if (URL.port == nil) { + [of_stderr writeLine: @"Need a port!"]; + [OFApplication terminateWithStatus: 1]; + } + + [sock connectToHost: URL.host + port: URL.port.uInt16Value]; + + return [OFPair pairWithFirstObject: sock + secondObject: sock]; + } + + [of_stderr writeFormat: @"Invalid protocol: %@\n", scheme]; + [OFApplication terminateWithStatus: 1]; + abort(); +} + +@implementation OFSock +- (void)applicationDidFinishLaunching +{ + OFArray OF_GENERIC(OFString *) *arguments = [OFApplication arguments]; + + if (arguments.count < 1) { + [of_stderr writeLine: @"Need at least one argument!"]; + [OFApplication terminateWithStatus: 1]; + } + + _streams = [[OFMutableArray alloc] init]; + + for (OFString *argument in arguments) { + OFPair *pair = streamFromString(argument); + + [pair.firstObject setDelegate: self]; + + [_streams addObject: pair]; + } + + if (arguments.count == 1) { + of_stdin.delegate = self; + + [_streams addObject: + [OFPair pairWithFirstObject: of_stdin + secondObject: of_stdout]]; + } + + for (OFPair *pair in _streams) + [pair.firstObject asyncReadIntoBuffer: _buffer + length: BUFFER_LEN]; +} + +- (void)removeDeadStream: (OFStream *)stream +{ + size_t count = _streams.count; + + for (size_t i = 0; i < count; i++) { + if ([[_streams objectAtIndex: i] firstObject] == stream) { + [_streams removeObjectAtIndex: i]; + break; + } + } + + if (_streams.count < 2) + [OFApplication terminateWithStatus: _errors]; +} + +- (bool)stream: (OFStream *)stream + didReadIntoBuffer: (void *)buffer + length: (size_t)length + exception: (id)exception +{ + if (exception != nil) { + [of_stderr writeFormat: @"Exception on stream %@: %@\n", + stream, exception]; + _errors++; + [self removeDeadStream: stream]; + return false; + } + + if (stream.atEndOfStream) { + [self removeDeadStream: stream]; + return false; + } + + for (OFPair *pair in _streams) { + if (pair.firstObject == stream) + continue; + + [pair.secondObject writeBuffer: buffer + length: length]; + } + + return true; +} +@end