Index: ps/trunk/source/lib/external_libraries/suppress_boost_warnings.h =================================================================== --- ps/trunk/source/lib/external_libraries/suppress_boost_warnings.h (revision 23415) +++ ps/trunk/source/lib/external_libraries/suppress_boost_warnings.h (revision 23416) @@ -1,61 +1,59 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // rationale: boost is only included from the PCH, but warnings are still // raised when templates are instantiated (e.g. vfs_lookup.cpp), // so include this header from such source files as well. #include "lib/sysdep/compiler.h" // ICC_VERSION #include "lib/sysdep/arch.h" // ARCH_IA32 #if MSC_VERSION # pragma warning(disable:4710) // function not inlined -#if _MSC_VER > 1800 # pragma warning(disable:4626) // assignment operator was implicitly defined as deleted because a base class assignment operator is inaccessible or deleted # pragma warning(disable:4625) // copy constructor was implicitly defined as deleted # pragma warning(disable:4668) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' # pragma warning(disable:5027) // 'type': move assignment operator was implicitly defined as deleted # pragma warning(disable:4365) // signed/unsigned mismatch # pragma warning(disable:4619) // there is no warning for 'warning' # pragma warning(disable:5031) // #pragma warning(pop): likely mismatch, popping warning state pushed in different file # pragma warning(disable:5026) // 'type': move constructor was implicitly defined as deleted # pragma warning(disable:4820) // incorrect padding # pragma warning(disable:4514) // unreferenced inlined function has been removed # pragma warning(disable:4571) // Informational: catch(...) semantics changed since Visual C++ 7.1; structured exceptions (SEH) are no longer caught #endif -#endif #if ICC_VERSION # pragma warning(push) # pragma warning(disable:82) // storage class is not first # pragma warning(disable:193) // zero used for undefined preprocessing identifier # pragma warning(disable:304) // access control not specified # pragma warning(disable:367) // duplicate friend declaration # pragma warning(disable:444) // destructor for base class is not virtual # pragma warning(disable:522) // function redeclared inline after being called # pragma warning(disable:811) // exception specification for implicitly declared virtual function is incompatible with that of overridden function # pragma warning(disable:1879) // unimplemented pragma ignored # pragma warning(disable:2270) // the declaration of the copy assignment operator has been suppressed # pragma warning(disable:2273) // the declaration of the copy constructor has been suppressed # if ARCH_IA32 # pragma warning(disable:693) // calling convention specified here is ignored # endif #endif Index: ps/trunk/source/lib/pch/pch_warnings.h =================================================================== --- ps/trunk/source/lib/pch/pch_warnings.h (revision 23415) +++ ps/trunk/source/lib/pch/pch_warnings.h (revision 23416) @@ -1,82 +1,76 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INCLUDED_PCH_WARNINGS #define INCLUDED_PCH_WARNINGS #include "lib/sysdep/compiler.h" // MSC_VERSION #if MSC_VERSION // .. unimportant but not entirely senseless W4 # pragma warning(disable:4201) // nameless struct (Matrix3D) # pragma warning(disable:4244) // conversion from uintN to uint8 // .. always disabled W4 # pragma warning(disable:4103) // alignment changed after including header (boost has #pragma pack/pop in separate headers) # pragma warning(disable:4127) // conditional expression is constant; rationale: see STMT in lib.h. # pragma warning(disable:4324) // structure was padded due to __declspec(align()) # pragma warning(disable:4574) // macro is defined to be 0 -#if MSC_VERSION <= 140 -# pragma warning(disable:4351) // yes, default init of array entries is desired -#endif # pragma warning(disable:4355) // 'this' used in base member initializer list # pragma warning(disable:4512) // assignment operator could not be generated # pragma warning(disable:4718) // recursive call has no side effects, deleting # pragma warning(disable:4786) // identifier truncated to 255 chars # pragma warning(disable:4996) // function is deprecated # pragma warning(disable:6011) // dereferencing NULL pointer # pragma warning(disable:6246) // local declaration hides declaration of the same name in outer scope # pragma warning(disable:6326) // potential comparison of a constant with another constant # pragma warning(disable:6334) // sizeof operator applied to an expression with an operator might yield unexpected results // .. Intel-specific # if ICC_VERSION # pragma warning(disable:383) // value copied to temporary, reference to temporary used # pragma warning(disable:981) // operands are evaluated in unspecified order # pragma warning(disable:1418) // external function definition with no prior declaration (raised for all non-static function templates) # pragma warning(disable:1572) // floating-point equality and inequality comparisons are unreliable # pragma warning(disable:1684) // conversion from pointer to same-sized integral type # pragma warning(disable:1786) // function is deprecated (disabling 4996 isn't sufficient) # pragma warning(disable:2415) // variable of static storage duration was declared but never referenced (raised by Boost) # endif // .. disabled by default in W4, ENABLE them # pragma warning(default:4062) // enumerator is not handled # pragma warning(default:4254) // [bit field] conversion, possible loss of data # pragma warning(default:4265) // class has virtual functions, but destructor is not virtual # pragma warning(default:4296) // [unsigned comparison vs. 0 =>] expression is always false # pragma warning(default:4545 4546 4547 4549) // ill-formed comma expressions; exclude 4548 since _SECURE_SCL triggers it frequently # pragma warning(default:4557) // __assume contains effect //# pragma warning(default:4710) // function not inlined (often happens in STL) -#if MSC_VERSION <= 140 -# pragma warning(default:4836) // local types or unnamed types cannot be used as template arguments -#endif # pragma warning(default:4905) // wide string literal cast to LPSTR # pragma warning(default:4906) // string literal cast to LPWSTR # pragma warning(default:4928) // illegal copy-initialization; more than one user-defined conversion has been implicitly applied # pragma warning(default:4946) // reinterpret_cast used between related classes // .. disabled by default in W4, leave them that way //# pragma warning(default:4191) // unsafe conversion (false alarms for function pointers) //# pragma warning(default:4263) // member function does not override any base class virtual member function (happens in GUI) //# pragma warning(default:4555) // expression has no effect (triggered by STL unused) //# pragma warning(default:4619) // #pragma warning: there is no [such] warning number (false alarms in STL) //# pragma warning(default:4668) // not defined as a preprocessor macro, replacing with '0' (frequent in Windows) #endif #endif // #ifndef INCLUDED_PCH_WARNINGS Index: ps/trunk/source/lib/precompiled.h =================================================================== --- ps/trunk/source/lib/precompiled.h (revision 23415) +++ ps/trunk/source/lib/precompiled.h (revision 23416) @@ -1,111 +1,114 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * precompiled header. must be the first non-comment part of every * source file (VC++ requirement). */ // some libraries have only a small number of source files, and the // overhead of including loads of headers here outweighs the improvements to // incremental rebuild performance. // they can set MINIMAL_PCH to 1 so we include far fewer headers (but // still do the global disabling of warnings and include config headers etc), // or set it to 2 to remove STL headers too (precompiling STL helps performance // in most non-tiny cases) #ifndef MINIMAL_PCH # define MINIMAL_PCH 0 #endif #include "lib/config.h" // CONFIG_ENABLE_BOOST, CONFIG_ENABLE_PCH #include "lib/sysdep/compiler.h" // MSC_VERSION // must come before any STL headers are included #if MSC_VERSION +# if MSC_VERSION < 1900 +# error "Visual Studio 2015 is the minimal supported version" +# endif # ifdef NDEBUG // release: disable all checks # define _HAS_ITERATOR_DEBUGGING 0 # define _SECURE_SCL 0 # endif #endif // disable some common and annoying warnings // (as soon as possible so that headers below are covered) #include "lib/pch/pch_warnings.h" #if ICC_VERSION #include // (must come before or (replaces them)) double __cdecl abs(double x); // not declared by mathimf #endif // // headers made available everywhere for convenience // #include "lib/posix/posix_types.h" // (must come before any system headers because it fixes off_t) #include "lib/code_annotation.h" #include "lib/sysdep/arch.h" #include "lib/sysdep/os.h" #include "lib/sysdep/stl.h" #include "lib/lib_api.h" #include "lib/types.h" #include "lib/debug.h" #include "lib/lib.h" #include "lib/secure_crt.h" #if CONFIG_ENABLE_BOOST # include "lib/pch/pch_boost.h" #endif #include #include using std::shared_ptr; // (must come after boost and common lib headers, but before re-enabling // warnings to avoid boost spew) #include "lib/posix/posix.h" // // precompiled headers // // if PCHs are supported and enabled, we make an effort to include all // system headers. otherwise, only a few central headers (e.g. types) // are pulled in and source files must include all the system headers // they use. this policy ensures good compile performance whether or not // PCHs are being used. #if CONFIG_ENABLE_PCH // anything placed here won't need to be compiled in each translation unit, // but will cause a complete rebuild if they change. #include "lib/pch/pch_stdlib.h" // These two files are included (directly or indirectly) by about half of the .cpp files. // Changing these thus forces recompilation of most of the project regardless, // and putting them in the precompiled header cuts a large amount of time even even on incremental builds. #include "ps/CLogger.h" #include "ps/Profile.h" #endif // #if CONFIG_ENABLE_PCH Index: ps/trunk/source/lib/sysdep/os/win/wdbg_heap.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg_heap.h (revision 23415) +++ ps/trunk/source/lib/sysdep/os/win/wdbg_heap.h (revision 23416) @@ -1,61 +1,59 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * improved debug heap using MS CRT */ #ifndef INCLUDED_WDBG_HEAP #define INCLUDED_WDBG_HEAP #ifdef _MSC_VER -#if _MSC_VER > 1800 # pragma warning(disable:4091) // hides previous local declaration #endif -#endif // this module provides a more convenient interface to the MS CRT's // debug heap checks. it also hooks into allocations to record the // caller/owner information without requiring macros (which break code // using placement new or member functions called free). /** * enable or disable manual and automatic heap validity checking. * (enabled by default during critical_init.) **/ LIB_API void wdbg_heap_Enable(bool); /** * check heap integrity. * errors are reported by the CRT or via debug_DisplayError. * no effect if called between wdbg_heap_Enable(false) and the next * wdbg_heap_Enable(true). **/ LIB_API void wdbg_heap_Validate(); /** * @return the total number of alloc and realloc operations thus far. * used by the in-game profiler. **/ LIB_API intptr_t wdbg_heap_NumberOfAllocations(); #endif // #ifndef INCLUDED_WDBG_HEAP Index: ps/trunk/source/lib/sysdep/os/win/wdbg_sym.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wdbg_sym.h (revision 23415) +++ ps/trunk/source/lib/sysdep/os/win/wdbg_sym.h (revision 23416) @@ -1,68 +1,66 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * Win32 stack trace and symbol engine. */ #ifndef INCLUDED_WDBG_SYM #define INCLUDED_WDBG_SYM #ifdef _MSC_VER -#if _MSC_VER > 1800 # pragma warning(disable:4091) // hides previous local declaration #endif -#endif #include "lib/sysdep/os/win/win.h" // CONTEXT, EXCEPTION_POINTERS struct _tagSTACKFRAME64; /** * called for each stack frame found by wdbg_sym_WalkStack. * * @param frame the dbghelp stack frame (we can't just pass the * instruction-pointer because dump_frame_cb needs the frame pointer to * locate frame-relative variables) * @param cbData the user-specified value that was passed to wdbg_sym_WalkStack * @return Status (see RETURN_STATUS_FROM_CALLBACK). **/ typedef Status (*StackFrameCallback)(const _tagSTACKFRAME64* frame, uintptr_t cbData); /** * Iterate over a call stack, invoking a callback for each frame encountered. * * @param cb * @param cbData * @param context Processor context from which to start (taken from * an exception record or debug_CaptureContext). * @param lastFuncToSkip * * @note It is safe to use ENSURE/debug_warn/WARN_RETURN_STATUS_IF_ERR even during a * stack trace (which is triggered by ENSURE et al. in app code) because * nested stack traces are ignored and only the error is displayed. **/ LIB_API Status wdbg_sym_WalkStack(StackFrameCallback cb, uintptr_t cbData, CONTEXT& context, const wchar_t* lastFuncToSkip = 0); LIB_API void wdbg_sym_WriteMinidump(EXCEPTION_POINTERS* ep); #endif // #ifndef INCLUDED_WDBG_SYM Index: ps/trunk/source/lib/sysdep/os/win/wposix/werrno.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wposix/werrno.h (revision 23415) +++ ps/trunk/source/lib/sysdep/os/win/wposix/werrno.h (revision 23416) @@ -1,141 +1,141 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef INCLUDED_WERRNO #define INCLUDED_WERRNO // // // #include // this is an exhaustive list of SUSv3 error codes; // see http://www.opengroup.org/onlinepubs/009695399/basedefs/errno.h.html . // defined by VC errno.h and SUSv3 // (commented out so that we use VC errno.h's already correct values) #if 0 #define EPERM 1 // Operation not permitted #define ENOENT 2 // No such file or directory #define ESRCH 3 // No such process #define EINTR 4 // Interrupted system call #define EIO 5 // I/O error #define ENXIO 6 // No such device or address #define E2BIG 7 // Argument list too long #define ENOEXEC 8 // Exec format error #define EBADF 9 // Bad file number #define ECHILD 10 // No child processes #define EAGAIN 11 // Try again #define ENOMEM 12 // Out of memory #define EACCES 13 // Permission denied #define EFAULT 14 // Bad address #define EBUSY 16 // Device or resource busy #define EEXIST 17 // File exists #define ENODEV 19 // No such device #define ENOTDIR 20 // Not a directory #define EISDIR 21 // Is a directory #define EINVAL 22 // Invalid argument #define ENFILE 23 // File table overflow #define EMFILE 24 // Too many open files #define ENOTTY 25 // Not a typewriter #define EFBIG 27 // File too large #define ENOSPC 28 // No space left on device #define ESPIPE 29 // Illegal seek #define EMLINK 31 // Too many links #define EPIPE 32 // Broken pipe #define EDOM 33 // Math argument out of domain of func #define ERANGE 34 // Math result not representable #endif // defined by VC errno.h and also SUSv3 (with different values) // (commented out because we must match the values used by the compiled CRT) #if 0 #define EDEADLK 35 // Resource deadlock would occur #define ENAMETOOLONG 36 // File name too long #define ENOLCK 37 // No record locks available #define ENOSYS 38 // Function not implemented #define ENOTEMPTY 39 // Directory not empty #define EILSEQ 84 // Illegal byte sequence #endif // defined by winsock2 and also Linux (with different values) // (values derived from winsock2 WSA* constants minus WSABASEERR) // update: disabled on newer Boost versions because filesystem drags in boost/cerrno.hpp -#if (!defined(BOOST_VERSION) || BOOST_VERSION <= 103401) && (!MSC_VERSION || MSC_VERSION < 1600) +#if (!defined(BOOST_VERSION) || BOOST_VERSION <= 103401) && !MSC_VERSION #define EWOULDBLOCK 35 #define EINPROGRESS 36 #define EALREADY 37 #define ENOTSOCK 38 #define EDESTADDRREQ 39 #define EMSGSIZE 40 #define EPROTOTYPE 41 #define ENOPROTOOPT 42 #define EPROTONOSUPPORT 43 #define EOPNOTSUPP 45 #define EAFNOSUPPORT 47 #define EADDRINUSE 48 #define EADDRNOTAVAIL 49 #define ENETDOWN 50 #define ENETUNREACH 51 #define ENETRESET 52 #define ECONNABORTED 53 #define ECONNRESET 54 #define ENOBUFS 55 #define EISCONN 56 #define ENOTCONN 57 #define ETIMEDOUT 60 #define ECONNREFUSED 61 #define EHOSTUNREACH 65 #define EDQUOT 69 #define ESTALE 70 #endif // defined by winsock2 but not Linux // (commented out because they're not portable) #if 0 #define ESOCKTNOSUPPORT 44 #define EPFNOSUPPORT 46 #define ESHUTDOWN 58 #define ETOOMANYREFS 59 #define ELOOP 62 #define EHOSTDOWN 64 #define EPROCLIM 67 #define EUSERS 68 #define EREMOTE 71 #endif // defined by Linux but not winsock2 // (commented out because they're not generated on Windows and thus // probably shouldn't be used) #if 0 #define ENOMSG 42 // No message of desired type #define EIDRM 43 // Identifier removed #define ENOLINK 67 // Reserved #define EPROTO 71 // Protocol error #define EMULTIHOP 72 // Reserved #define EBADMSG 74 // Not a data message #define EOVERFLOW 75 // Value too large for defined data type #define ECANCELED 125 // Operation Canceled #endif #endif // #ifndef INCLUDED_WERRNO Index: ps/trunk/source/lib/sysdep/os/win/wutil.h =================================================================== --- ps/trunk/source/lib/sysdep/os/win/wutil.h (revision 23415) +++ ps/trunk/source/lib/sysdep/os/win/wutil.h (revision 23416) @@ -1,211 +1,209 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * various Windows-specific utilities */ #ifndef INCLUDED_WUTIL #define INCLUDED_WUTIL #ifdef _MSC_VER -#if _MSC_VER > 1800 # pragma warning(disable:4091) // hides previous local declaration #endif -#endif #include "lib/os_path.h" #include "lib/sysdep/os/win/win.h" template bool wutil_IsValidHandle(H h) { return h != 0 && h != INVALID_HANDLE_VALUE; } //----------------------------------------------------------------------------- // dynamic linking // define a function pointer (optionally prepend 'static') #define WUTIL_FUNC(varName, ret, params)\ ret (WINAPI* varName) params // rationale: // - splitting up WUTIL_FUNC and WUTIL_IMPORT is a bit verbose in // the common case of a local function pointer definition, // but allows one-time initialization of static variables. // - differentiating between procName and varName allows searching // for the actual definition of the function pointer in the code. // - a cast would require passing in ret/params. // - writing a type-punned pointer breaks strict-aliasing rules. #define WUTIL_IMPORT(hModule, procName, varName)\ STMT(\ const FARPROC f = GetProcAddress(hModule, #procName);\ memcpy(&varName, &f, sizeof(FARPROC));\ ) // note: Kernel32 is guaranteed to be loaded, so we don't // need to LoadLibrary and FreeLibrary. #define WUTIL_IMPORT_KERNEL32(procName, varName)\ WUTIL_IMPORT(GetModuleHandleW(L"kernel32.dll"), procName, varName) //----------------------------------------------------------------------------- // safe allocator extern void* wutil_Allocate(size_t size); extern void wutil_Free(void* p); //----------------------------------------------------------------------------- // locks // critical sections used by win-specific code enum WinLockId { WDBG_SYM_CS, // protects (non-reentrant) dbghelp.dll WDIR_WATCH_CS, NUM_CS }; extern void wutil_Lock(WinLockId id); extern void wutil_Unlock(WinLockId id); // used in a desperate attempt to avoid deadlock in wseh. extern bool wutil_IsLocked(WinLockId id); class WinScopedLock { public: WinScopedLock(WinLockId id) : m_id(id) { wutil_Lock(m_id); } ~WinScopedLock() { wutil_Unlock(m_id); } private: WinLockId m_id; }; //----------------------------------------------------------------------------- // errors /** * some WinAPI functions SetLastError(0) on success, which is bad because * it can hide previous errors. this class takes care of restoring the * previous value. **/ class WinScopedPreserveLastError { public: WinScopedPreserveLastError() : m_lastError(GetLastError()) { SetLastError(0); } ~WinScopedPreserveLastError() { if(m_lastError != 0 && GetLastError() == 0) SetLastError(m_lastError); } private: DWORD m_lastError; }; /** * @return the Status equivalent of GetLastError(), or ERR::FAIL if * there's no equivalent. * SetLastError(0) should be called before the Windows function to * make sure no stale errors are returned. **/ extern Status StatusFromWin(); //----------------------------------------------------------------------------- // command line extern int wutil_argc(); extern wchar_t** wutil_argv(); extern bool wutil_HasCommandLineArgument(const wchar_t* arg); //----------------------------------------------------------------------------- // directories extern const OsPath& wutil_SystemPath(); extern const OsPath& wutil_ExecutablePath(); extern const OsPath& wutil_LocalAppdataPath(); extern const OsPath& wutil_RoamingAppdataPath(); extern const OsPath& wutil_PersonalPath(); //----------------------------------------------------------------------------- // Wow64 extern bool wutil_IsWow64(); class WinScopedDisableWow64Redirection { public: WinScopedDisableWow64Redirection(); ~WinScopedDisableWow64Redirection(); private: void* m_wasRedirectionEnabled; }; //----------------------------------------------------------------------------- LIB_API Status wutil_SetPrivilege(const wchar_t* privilege, bool enable); /** * @return module handle of lib code (that of the main EXE if * linked statically, otherwise the DLL). * this is necessary for the error dialog. **/ extern HMODULE wutil_LibModuleHandle(); /** * @return handle to the first window owned by the current process, or * 0 if none exist (e.g. it hasn't yet created one). * * enumerates all top-level windows and stops if PID matches. * once this function returns a non-NULL handle, it will always * return that cached value. **/ extern HWND wutil_AppWindow(); #endif // #ifndef INCLUDED_WUTIL Index: ps/trunk/source/lib/sysdep/sysdep.h =================================================================== --- ps/trunk/source/lib/sysdep/sysdep.h (revision 23415) +++ ps/trunk/source/lib/sysdep/sysdep.h (revision 23416) @@ -1,194 +1,192 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* * various system-specific function implementations */ #ifndef INCLUDED_SYSDEP #define INCLUDED_SYSDEP #ifdef _MSC_VER -#if _MSC_VER > 1800 # pragma warning(disable:4091) // hides previous local declaration #endif -#endif #include "lib/debug.h" // ErrorReactionInternal #include "lib/os_path.h" // // output // /** * display a message. * * @param caption title message * @param msg message contents * * implemented as a MessageBox on Win32 and printf on Unix. * called from debug_DisplayMessage. **/ extern void sys_display_msg(const wchar_t* caption, const wchar_t* msg); /** * show the error dialog. * * @param text to display (practically unlimited length) * @param flags: see DebugDisplayErrorFlags. * @return ErrorReactionInternal (except ERI_EXIT, which is acted on immediately) * * called from debug_DisplayError unless overridden by means of * ah_display_error. **/ extern ErrorReactionInternal sys_display_error(const wchar_t* text, size_t flags); // // misc // /** * @return whether a debugger is attached to the process * (if so, it is safe to use debug_break; otherwise, that would * raise an exception) **/ LIB_API bool sys_IsDebuggerPresent(); /** * @return a wide string conversion of the platform's encoding of main's argv. * * (NB: wseh.cpp defines a wmain that converts argv to UTF-8 and calls main(), * but only if LIB_STATIC_LINK) **/ LIB_API std::wstring sys_WideFromArgv(const char* argv_i); /** * describe the current OS error state. * * @param err: if not 0, use that as the error code to translate; otherwise, * uses GetLastError or similar. * @param buf output buffer * @param max_chars * * rationale: it is expected to be rare that OS return/error codes are * actually seen by user code, but we leave the possibility open. **/ extern Status sys_StatusDescription(int err, wchar_t* buf, size_t max_chars); /** * determine filename of the module to whom an address belongs. * * @param addr * @param pathname Full path to module (unchanged unless INFO::OK is returned). * @return Status * * note: this is useful for handling exceptions in other modules. **/ Status sys_get_module_filename(void* addr, OsPath& pathname); /** * @return full pathname of the current executable. * * this is useful for determining installation directory, e.g. for VFS. **/ LIB_API OsPath sys_ExecutablePathname(); /** * Get the current user's login name. * * @return login name, or empty string on error */ extern std::wstring sys_get_user_name(); /** * Have the user choose a directory via OS dialog. * * @param path Path's input value determines the starting directory for * faster browsing. if INFO::OK is returned, it receives * chosen directory path. **/ extern Status sys_pick_directory(OsPath& path); /** * Open the user's default web browser to the given URL. **/ extern Status sys_open_url(const std::string& url); /** * return the largest sector size [bytes] of any storage medium * (HD, optical, etc.) in the system. * * this may be a bit slow to determine (iterates over all drives), * but caches the result so subsequent calls are free. * (caveat: device changes won't be noticed during this program run) * * sector size is relevant because Windows aio requires all IO * buffers, offsets and lengths to be a multiple of it. this requirement * is also carried over into the vfs / file.cpp interfaces for efficiency * (avoids the need for copying to/from align buffers). * * waio uses the sector size to (in some cases) align IOs if * they aren't already, but it's also needed by user code when * aligning their buffers to meet the requirements. * * the largest size is used so that we can read from any drive. while this * is a bit wasteful (more padding) and requires iterating over all drives, * it is the only safe way: this may be called before we know which * drives will be needed, and hardlinks may confuse things. **/ extern size_t sys_max_sector_size(); /** * generate high-quality random bytes. * * this should only be used with small numbers of bytes, to avoid * hogging the system's entropy. **/ LIB_API Status sys_generate_random_bytes(u8* buf, size_t count); /** * get the proxy address for accessing the given HTTP URL. * * this may be very slow (tens of seconds). * * @return INFO::OK on success; INFO::SKIPPED if no proxy found. **/ LIB_API Status sys_get_proxy_config(const std::wstring& url, std::wstring& proxy); /** * open a file like with fopen (but taking an OsPath argument). */ LIB_API FILE* sys_OpenFile(const OsPath& pathname, const char* mode); /** * directory separation character **/ #if OS_WIN # define SYS_DIR_SEP '\\' #else # define SYS_DIR_SEP '/' #endif #endif // #ifndef INCLUDED_SYSDEP Index: ps/trunk/source/ps/LoaderThunks.h =================================================================== --- ps/trunk/source/ps/LoaderThunks.h (revision 23415) +++ ps/trunk/source/ps/LoaderThunks.h (revision 23416) @@ -1,87 +1,77 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_LOADERTHUNKS #define INCLUDED_LOADERTHUNKS -// VC7 warns if T::*func is not aligned to its size (4..16 bytes on IA-32). -// this is a bug, since sizeof(void*) would be enough. MS says it won't -// be fixed: see http://www.dotnet247.com/247reference/msgs/1/7782.aspx -// we don't make sure alignment is acceptable because both 12 and 16 bytes -// may be required and padding to LCM(12,16) bytes would be wasteful; -// therefore, just disable the warning. -#if MSC_VERSION -#pragma warning(disable: 4121) -#endif - // does this return code indicate the coroutine yielded and // wasn't yet finished? static inline bool ldr_was_interrupted(int ret) { return (0 < ret && ret <= 100); } template struct MemFun_t { NONCOPYABLE(MemFun_t); public: T* const this_; int (T::*func)(void); MemFun_t(T* this__, int(T::*func_)(void)) : this_(this__), func(func_) {} }; template static int MemFunThunk(std::shared_ptr param, double UNUSED(time_left)) { MemFun_t* const mf = static_cast*>(param.get()); return (mf->this_->*mf->func)(); } template void RegMemFun(T* this_, int(T::*func)(void), const wchar_t* description, int estimated_duration_ms) { LDR_Register(MemFunThunk, std::make_shared>(this_, func), description, estimated_duration_ms); } //////////////////////////////////////////////////////// template struct MemFun1_t { NONCOPYABLE(MemFun1_t); public: T* const this_; Arg arg; int (T::*func)(Arg); MemFun1_t(T* this__, int(T::*func_)(Arg), Arg arg_) : this_(this__), func(func_), arg(arg_) {} }; template static int MemFun1Thunk(shared_ptr param, double UNUSED(time_left)) { MemFun1_t* const mf = static_cast*>(param.get()); return (mf->this_->*mf->func)(mf->arg); } template void RegMemFun1(T* this_, int(T::*func)(Arg), Arg arg, const wchar_t* description, int estimated_duration_ms) { LDR_Register(MemFun1Thunk, std::make_shared >(this_, func, arg), description, estimated_duration_ms); } #endif // INCLUDED_LOADERTHUNKS Index: ps/trunk/source/third_party/cppformat/format.cpp =================================================================== --- ps/trunk/source/third_party/cppformat/format.cpp (revision 23415) +++ ps/trunk/source/third_party/cppformat/format.cpp (revision 23416) @@ -1,1315 +1,1313 @@ /* * Slightly modified version of cppformat, by Wildfire Games, for 0 A.D. * Based on cppformat v0.11.0 from https://github.com/cppformat/cppformat */ /* Formatting library for C++ Copyright (c) 2012 - 2014, Victor Zverovich All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "precompiled.h" // Disable useless MSVC warnings. #undef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #undef _SCL_SECURE_NO_WARNINGS #define _SCL_SECURE_NO_WARNINGS #include "format.h" #include #include #include #include #include #include #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # ifdef __MINGW32__ # include # endif # include # undef ERROR #endif using fmt::LongLong; using fmt::ULongLong; using fmt::internal::Arg; namespace { #ifndef _MSC_VER // Portable version of signbit. // When compiled in C++11 mode signbit is no longer a macro but a function // defined in namespace std and the macro is undefined. inline int getsign(double x) { #ifdef signbit return signbit(x); #else return std::signbit(x); #endif } // Portable version of isinf. #ifdef isinf inline int isinfinity(double x) { return isinf(x); } inline int isinfinity(long double x) { return isinf(x); } #else inline int isinfinity(double x) { return std::isinf(x); } inline int isinfinity(long double x) { return std::isinf(x); } #endif #define FMT_SNPRINTF snprintf #else // _MSC_VER # pragma warning(push) # pragma warning(disable: 4127) // conditional expression is constant -#if _MSC_VER > 1800 # pragma warning(disable:4456) // hides previous local declaration -#endif inline int getsign(double value) { if (value < 0) return 1; if (value == value) return 0; int dec = 0, sign = 0; char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. _ecvt_s(buffer, sizeof(buffer), value, 0, &dec, &sign); return sign; } inline int isinfinity(double x) { return !_finite(x); } inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { va_list args; va_start(args, format); int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); va_end(args); return result; } #define FMT_SNPRINTF fmt_snprintf #endif // _MSC_VER template struct IsLongDouble { enum {VALUE = 0}; }; template <> struct IsLongDouble { enum {VALUE = 1}; }; // Checks if a value fits in int - used to avoid warnings about comparing // signed and unsigned integers. template struct IntChecker { template static bool fits_in_int(T value) { unsigned max = INT_MAX; return value <= max; } }; template <> struct IntChecker { template static bool fits_in_int(T value) { return value >= INT_MIN && value <= INT_MAX; } }; const char RESET_COLOR[] = "\x1b[0m"; typedef void (*FormatFunc)(fmt::Writer &, int , fmt::StringRef); void report_error(FormatFunc func, int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { try { fmt::Writer full_message; func(full_message, error_code, message); // TODO: make sure this doesn't throw std::fwrite(full_message.c_str(), full_message.size(), 1, stderr); std::fputc('\n', stderr); } catch (...) {} } const Arg DUMMY_ARG = {Arg::INT, {0}}; // IsZeroInt::visit(arg) returns true iff arg is a zero integer. class IsZeroInt : public fmt::internal::ArgVisitor { public: template bool visit_any_int(T value) { return value == 0; } }; // Parses an unsigned integer advancing s to the end of the parsed input. // This function assumes that the first character of s is a digit. template int parse_nonnegative_int( const Char *&s, const char *&error) FMT_NOEXCEPT(true) { assert('0' <= *s && *s <= '9'); unsigned value = 0; do { unsigned new_value = value * 10 + (*s++ - '0'); // Check if value wrapped around. value = new_value >= value ? new_value : UINT_MAX; } while ('0' <= *s && *s <= '9'); if (value > INT_MAX) { if (!error) error = "number is too big in format"; return 0; } return value; } template const Char *find_closing_brace(const Char *s, int num_open_braces = 1) { for (int n = num_open_braces; *s; ++s) { if (*s == '{') { ++n; } else if (*s == '}') { if (--n == 0) return s; } } throw fmt::FormatError("unmatched '{' in format"); } // Checks if an argument is a valid printf width specifier and sets // left alignment if it is negative. class WidthHandler : public fmt::internal::ArgVisitor { private: fmt::FormatSpec &spec_; public: explicit WidthHandler(fmt::FormatSpec &spec) : spec_(spec) {} unsigned visit_unhandled_arg() { throw fmt::FormatError("width is not integer"); } template unsigned visit_any_int(T value) { typedef typename fmt::internal::IntTraits::MainType UnsignedType; UnsignedType width = value; if (fmt::internal::is_negative(value)) { spec_.align_ = fmt::ALIGN_LEFT; width = 0 - width; } if (width > INT_MAX) throw fmt::FormatError("number is too big in format"); return static_cast(width); } }; class PrecisionHandler : public fmt::internal::ArgVisitor { public: unsigned visit_unhandled_arg() { throw fmt::FormatError("precision is not integer"); } template int visit_any_int(T value) { if (!IntChecker::is_signed>::fits_in_int(value)) throw fmt::FormatError("number is too big in format"); return static_cast(value); } }; // Converts an integer argument to type T. template class ArgConverter : public fmt::internal::ArgVisitor, void> { private: fmt::internal::Arg &arg_; wchar_t type_; public: ArgConverter(fmt::internal::Arg &arg, wchar_t type) : arg_(arg), type_(type) {} template void visit_any_int(U value) { bool is_signed = type_ == 'd' || type_ == 'i'; using fmt::internal::Arg; if (sizeof(T) <= sizeof(int)) { if (is_signed) { arg_.type = Arg::INT; arg_.int_value = static_cast(static_cast(value)); } else { arg_.type = Arg::UINT; arg_.uint_value = static_cast( static_cast::Type>(value)); } } else { if (is_signed) { arg_.type = Arg::LONG_LONG; arg_.long_long_value = static_cast::Type>(value); } else { arg_.type = Arg::ULONG_LONG; arg_.ulong_long_value = static_cast::Type>(value); } } } }; // Converts an integer argument to char. class CharConverter : public fmt::internal::ArgVisitor { private: fmt::internal::Arg &arg_; public: explicit CharConverter(fmt::internal::Arg &arg) : arg_(arg) {} template void visit_any_int(T value) { arg_.type = Arg::CHAR; arg_.int_value = static_cast(value); } }; // This function template is used to prevent compile errors when handling // incompatible string arguments, e.g. handling a wide string in a narrow // string formatter. template Arg::StringValue ignore_incompatible_str(Arg::StringValue); template <> inline Arg::StringValue ignore_incompatible_str( Arg::StringValue) { return Arg::StringValue(); } template <> inline Arg::StringValue ignore_incompatible_str( Arg::StringValue s) { return s; } } // namespace void fmt::SystemError::init( int error_code, StringRef format_str, const ArgList &args) { error_code_ = error_code; Writer w; internal::format_system_error(w, error_code, format(format_str, args)); std::runtime_error &base = *this; base = std::runtime_error(w.str()); } template int fmt::internal::CharTraits::format_float( char *buffer, std::size_t size, const char *format, unsigned width, int precision, T value) { if (width == 0) { return precision < 0 ? FMT_SNPRINTF(buffer, size, format, value) : FMT_SNPRINTF(buffer, size, format, precision, value); } return precision < 0 ? FMT_SNPRINTF(buffer, size, format, width, value) : FMT_SNPRINTF(buffer, size, format, width, precision, value); } template int fmt::internal::CharTraits::format_float( wchar_t *buffer, std::size_t size, const wchar_t *format, unsigned width, int precision, T value) { if (width == 0) { return precision < 0 ? swprintf(buffer, size, format, value) : swprintf(buffer, size, format, precision, value); } return precision < 0 ? swprintf(buffer, size, format, width, value) : swprintf(buffer, size, format, width, precision, value); } const char fmt::internal::DIGITS[] = "0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" "4041424344454647484950515253545556575859" "6061626364656667686970717273747576777879" "8081828384858687888990919293949596979899"; #define FMT_POWERS_OF_10(factor) \ factor * 10, \ factor * 100, \ factor * 1000, \ factor * 10000, \ factor * 100000, \ factor * 1000000, \ factor * 10000000, \ factor * 100000000, \ factor * 1000000000 const uint32_t fmt::internal::POWERS_OF_10_32[] = {0, FMT_POWERS_OF_10(1)}; const uint64_t fmt::internal::POWERS_OF_10_64[] = { 0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(ULongLong(1000000000)), // Multiply several constants instead of using a single long long constant // to avoid warnings about C++98 not supporting long long. ULongLong(1000000000) * ULongLong(1000000000) * 10 }; void fmt::internal::report_unknown_type(char code, const char *type) { if (std::isprint(static_cast(code))) { throw fmt::FormatError( fmt::format("unknown format code '{}' for {}", code, type)); } throw fmt::FormatError( fmt::format("unknown format code '\\x{:02x}' for {}", static_cast(code), type)); } #ifdef _WIN32 fmt::internal::UTF8ToUTF16::UTF8ToUTF16(fmt::StringRef s) { int length = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, 0, 0); static const char ERROR[] = "cannot convert string from UTF-8 to UTF-16"; if (length == 0) throw WindowsError(GetLastError(), ERROR); buffer_.resize(length); length = MultiByteToWideChar( CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, &buffer_[0], length); if (length == 0) throw WindowsError(GetLastError(), ERROR); } fmt::internal::UTF16ToUTF8::UTF16ToUTF8(fmt::WStringRef s) { if (int error_code = convert(s)) { throw WindowsError(error_code, "cannot convert string from UTF-16 to UTF-8"); } } int fmt::internal::UTF16ToUTF8::convert(fmt::WStringRef s) { int length = WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, 0, 0, 0, 0); if (length == 0) return GetLastError(); buffer_.resize(length); length = WideCharToMultiByte( CP_UTF8, 0, s.c_str(), -1, &buffer_[0], length, 0, 0); if (length == 0) return GetLastError(); return 0; } void fmt::WindowsError::init( int error_code, StringRef format_str, const ArgList &args) { error_code_ = error_code; Writer w; internal::format_windows_error(w, error_code, format(format_str, args)); std::runtime_error &base = *this; base = std::runtime_error(w.str()); } #endif int fmt::internal::safe_strerror( int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT(true) { assert(buffer != 0 && buffer_size != 0); int result = 0; #if defined(_GNU_SOURCE) && !defined(__BIONIC__) char *message = strerror_r(error_code, buffer, buffer_size); // If the buffer is full then the message is probably truncated. if (message == buffer && strlen(buffer) == buffer_size - 1) result = ERANGE; buffer = message; #elif defined(__MINGW32__) errno = 0; (void)buffer_size; buffer = strerror(error_code); result = errno; #elif defined(_WIN32) result = strerror_s(buffer, buffer_size, error_code); // If the buffer is full then the message is probably truncated. if (result == 0 && std::strlen(buffer) == buffer_size - 1) result = ERANGE; #else result = strerror_r(error_code, buffer, buffer_size); if (result == -1) result = errno; // glibc versions before 2.13 return result in errno. #endif return result; } void fmt::internal::format_system_error( fmt::Writer &out, int error_code, fmt::StringRef message) { Array buffer; buffer.resize(INLINE_BUFFER_SIZE); char *system_message = 0; for (;;) { system_message = &buffer[0]; int result = safe_strerror(error_code, system_message, buffer.size()); if (result == 0) break; if (result != ERANGE) { // Can't get error message, report error code instead. out << message << ": error code = " << error_code; return; } buffer.resize(buffer.size() * 2); } out << message << ": " << system_message; } #ifdef _WIN32 void fmt::internal::format_windows_error( fmt::Writer &out, int error_code, fmt::StringRef message) { class String { private: LPWSTR str_; public: String() : str_() {} ~String() { LocalFree(str_); } LPWSTR *ptr() { return &str_; } LPCWSTR c_str() const { return str_; } }; String system_message; if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(system_message.ptr()), 0, 0)) { UTF16ToUTF8 utf8_message; if (!utf8_message.convert(system_message.c_str())) { out << message << ": " << utf8_message; return; } } // Can't get error message, report error code instead. out << message << ": error code = " << error_code; } #endif // An argument formatter. template class fmt::internal::ArgFormatter : public fmt::internal::ArgVisitor, void> { private: fmt::BasicFormatter &formatter_; fmt::BasicWriter &writer_; fmt::FormatSpec &spec_; const Char *format_; public: ArgFormatter( fmt::BasicFormatter &f,fmt::FormatSpec &s, const Char *fmt) : formatter_(f), writer_(f.writer()), spec_(s), format_(fmt) {} template void visit_any_int(T value) { writer_.write_int(value, spec_); } template void visit_any_double(T value) { writer_.write_double(value, spec_); } void visit_char(int value) { if (spec_.type_ && spec_.type_ != 'c') { spec_.flags_ |= CHAR_FLAG; writer_.write_int(value, spec_); return; } if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0) throw FormatError("invalid format specifier for char"); typedef typename fmt::BasicWriter::CharPtr CharPtr; CharPtr out = CharPtr(); if (spec_.width_ > 1) { Char fill = static_cast(spec_.fill()); out = writer_.grow_buffer(spec_.width_); if (spec_.align_ == fmt::ALIGN_RIGHT) { std::fill_n(out, spec_.width_ - 1, fill); out += spec_.width_ - 1; } else if (spec_.align_ == fmt::ALIGN_CENTER) { out = writer_.fill_padding(out, spec_.width_, 1, fill); } else { std::fill_n(out + 1, spec_.width_ - 1, fill); } } else { out = writer_.grow_buffer(1); } *out = static_cast(value); } void visit_string(Arg::StringValue value) { writer_.write_str(value, spec_); } void visit_wstring(Arg::StringValue value) { writer_.write_str(ignore_incompatible_str(value), spec_); } void visit_pointer(const void *value) { if (spec_.type_ && spec_.type_ != 'p') fmt::internal::report_unknown_type(spec_.type_, "pointer"); spec_.flags_ = fmt::HASH_FLAG; spec_.type_ = 'x'; writer_.write_int(reinterpret_cast(value), spec_); } void visit_custom(Arg::CustomValue c) { c.format(&formatter_, c.value, format_); } }; template void fmt::internal::FormatErrorReporter::operator()( const Char *s, fmt::StringRef message) const { if (find_closing_brace(s, num_open_braces)) throw fmt::FormatError(message); } // Fills the padding around the content and returns the pointer to the // content area. template typename fmt::BasicWriter::CharPtr fmt::BasicWriter::fill_padding(CharPtr buffer, unsigned total_size, std::size_t content_size, wchar_t fill) { std::size_t padding = total_size - content_size; std::size_t left_padding = padding / 2; Char fill_char = static_cast(fill); std::fill_n(buffer, left_padding, fill_char); buffer += left_padding; CharPtr content = buffer; std::fill_n(buffer + content_size, padding - left_padding, fill_char); return content; } template template void fmt::BasicWriter::write_double(T value, const FormatSpec &spec) { // Check type. char type = spec.type(); bool upper = false; switch (type) { case 0: type = 'g'; break; case 'e': case 'f': case 'g': case 'a': break; case 'F': #ifdef _MSC_VER // MSVC's printf doesn't support 'F'. type = 'f'; #endif // Fall through. case 'E': case 'G': case 'A': upper = true; break; default: internal::report_unknown_type(type, "double"); break; } char sign = 0; // Use getsign instead of value < 0 because the latter is always // false for NaN. if (getsign(static_cast(value))) { sign = '-'; value = -value; } else if (spec.flag(SIGN_FLAG)) { sign = spec.flag(PLUS_FLAG) ? '+' : ' '; } if (value != value) { // Format NaN ourselves because sprintf's output is not consistent // across platforms. std::size_t size = 4; const char *nan = upper ? " NAN" : " nan"; if (!sign) { --size; ++nan; } CharPtr out = write_str(nan, size, spec); if (sign) *out = sign; return; } if (isinfinity(value)) { // Format infinity ourselves because sprintf's output is not consistent // across platforms. std::size_t size = 4; const char *inf = upper ? " INF" : " inf"; if (!sign) { --size; ++inf; } CharPtr out = write_str(inf, size, spec); if (sign) *out = sign; return; } std::size_t offset = buffer_.size(); unsigned width = spec.width(); if (sign) { buffer_.reserve(buffer_.size() + (std::max)(width, 1u)); if (width > 0) --width; ++offset; } // Build format string. enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg Char format[MAX_FORMAT_SIZE]; Char *format_ptr = format; *format_ptr++ = '%'; unsigned width_for_sprintf = width; if (spec.flag(HASH_FLAG)) *format_ptr++ = '#'; if (spec.align() == ALIGN_CENTER) { width_for_sprintf = 0; } else { if (spec.align() == ALIGN_LEFT) *format_ptr++ = '-'; if (width != 0) *format_ptr++ = '*'; } if (spec.precision() >= 0) { *format_ptr++ = '.'; *format_ptr++ = '*'; } if (IsLongDouble::VALUE) *format_ptr++ = 'L'; *format_ptr++ = type; *format_ptr = '\0'; // Format using snprintf. Char fill = static_cast(spec.fill()); for (;;) { std::size_t size = buffer_.capacity() - offset; #ifdef _MSC_VER // MSVC's vsnprintf_s doesn't work with zero size, so reserve // space for at least one extra character to make the size non-zero. // Note that the buffer's capacity will increase by more than 1. if (size == 0) { buffer_.reserve(offset + 1); size = buffer_.capacity() - offset; } #endif Char *start = &buffer_[offset]; int n = internal::CharTraits::format_float( start, size, format, width_for_sprintf, spec.precision(), value); if (n >= 0 && offset + n < buffer_.capacity()) { if (sign) { if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || *start != ' ') { *(start - 1) = sign; sign = 0; } else { *(start - 1) = fill; } ++n; } if (spec.align() == ALIGN_CENTER && spec.width() > static_cast(n)) { unsigned width = spec.width(); CharPtr p = grow_buffer(width); std::copy(p, p + n, p + (width - n) / 2); fill_padding(p, spec.width(), n, fill); return; } if (spec.fill() != ' ' || sign) { while (*start == ' ') *start++ = fill; if (sign) *(start - 1) = sign; } grow_buffer(n); return; } // If n is negative we ask to increase the capacity by at least 1, // but as std::vector, the buffer grows exponentially. buffer_.reserve(n >= 0 ? offset + n + 1 : buffer_.capacity() + 1); } } template template void fmt::BasicWriter::write_str( const Arg::StringValue &str, const FormatSpec &spec) { // Check if StrChar is convertible to Char. internal::CharTraits::convert(StrChar()); if (spec.type_ && spec.type_ != 's') internal::report_unknown_type(spec.type_, "string"); const StrChar *s = str.value; std::size_t size = str.size; if (size == 0) { if (!s) { Char err[] = { '(', 'n', 'u', 'l', 'l', ')' }; write_str(err, sizeof(err)/sizeof(Char), spec); return; } if (*s) size = std::char_traits::length(s); } write_str(s, size, spec); } template inline const Arg &fmt::BasicFormatter::parse_arg_index(const Char *&s) { unsigned arg_index = 0; if (*s < '0' || *s > '9') { if (*s != '}' && *s != ':') report_error_(s, "invalid argument index in format string"); const Arg &arg = next_arg(); if (error_) report_error_(s, error_); return arg; } if (next_arg_index_ > 0) { report_error_(s, "cannot switch from automatic to manual argument indexing"); } next_arg_index_ = -1; arg_index = parse_nonnegative_int(s, error_); if (error_) report_error_(s, error_); // TODO: don't use report_error_ if (arg_index >= args_.size()) report_error_(s, "argument index is out of range in format"); return args_[arg_index]; } template void fmt::BasicFormatter::check_sign( const Char *&s, const Arg &arg) { char sign = static_cast(*s); if (arg.type > Arg::LAST_NUMERIC_TYPE) { report_error_(s, fmt::format( "format specifier '{}' requires numeric argument", sign).c_str()); } if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) { report_error_(s, fmt::format( "format specifier '{}' requires signed argument", sign).c_str()); } ++s; } const Arg &fmt::internal::FormatterBase::next_arg() { if (next_arg_index_ < 0) { if (!error_) error_ = "cannot switch from manual to automatic argument indexing"; return DUMMY_ARG; } unsigned arg_index = next_arg_index_++; if (arg_index < args_.size()) return args_[arg_index]; if (!error_) error_ = "argument index is out of range in format"; return DUMMY_ARG; } const Arg &fmt::internal::FormatterBase::handle_arg_index(unsigned arg_index) { if (arg_index != UINT_MAX) { if (next_arg_index_ <= 0) { next_arg_index_ = -1; --arg_index; } else if (!error_) { error_ = "cannot switch from automatic to manual argument indexing"; } if (arg_index < args_.size()) return args_[arg_index]; if (!error_) error_ = "argument index is out of range in format"; return DUMMY_ARG; } return next_arg(); } template void fmt::internal::PrintfFormatter::parse_flags( FormatSpec &spec, const Char *&s) { for (;;) { switch (*s++) { case '-': spec.align_ = ALIGN_LEFT; break; case '+': spec.flags_ |= SIGN_FLAG | PLUS_FLAG; break; case '0': spec.fill_ = '0'; break; case ' ': spec.flags_ |= SIGN_FLAG; break; case '#': spec.flags_ |= HASH_FLAG; break; default: --s; return; } } } template unsigned fmt::internal::PrintfFormatter::parse_header( const Char *&s, FormatSpec &spec) { unsigned arg_index = UINT_MAX; Char c = *s; if (c >= '0' && c <= '9') { // Parse an argument index (if followed by '$') or a width possibly // preceded with '0' flag(s). unsigned value = parse_nonnegative_int(s, error_); if (*s == '$') { // value is an argument index ++s; arg_index = value; } else { if (c == '0') spec.fill_ = '0'; if (value != 0) { // Nonzero value means that we parsed width and don't need to // parse it or flags again, so return now. spec.width_ = value; return arg_index; } } } parse_flags(spec, s); // Parse width. if (*s >= '0' && *s <= '9') { spec.width_ = parse_nonnegative_int(s, error_); } else if (*s == '*') { ++s; spec.width_ = WidthHandler(spec).visit(handle_arg_index(UINT_MAX)); } return arg_index; } template void fmt::internal::PrintfFormatter::format( BasicWriter &writer, BasicStringRef format, const ArgList &args) { const Char *start = format.c_str(); args_ = args; next_arg_index_ = 0; const Char *s = start; while (*s) { Char c = *s++; if (c != '%') continue; if (*s == c) { write(writer, start, s); start = ++s; continue; } write(writer, start, s - 1); FormatSpec spec; spec.align_ = ALIGN_RIGHT; // Reporting errors is delayed till the format specification is // completely parsed. This is done to avoid potentially confusing // error messages for incomplete format strings. For example, in // sprintf("%2$", 42); // the format specification is incomplete. In a naive approach we // would parse 2 as an argument index and report an error that the // index is out of range which would be rather confusing if the // use meant "%2d$" rather than "%2$d". If we delay an error, the // user will get an error that the format string is invalid which // is OK for both cases. // Parse argument index, flags and width. unsigned arg_index = parse_header(s, spec); // Parse precision. if (*s == '.') { ++s; if ('0' <= *s && *s <= '9') { spec.precision_ = parse_nonnegative_int(s, error_); } else if (*s == '*') { ++s; spec.precision_ = PrecisionHandler().visit(handle_arg_index(UINT_MAX)); } } Arg arg = handle_arg_index(arg_index); if (spec.flag(HASH_FLAG) && IsZeroInt().visit(arg)) spec.flags_ &= ~HASH_FLAG; if (spec.fill_ == '0') { if (arg.type <= Arg::LAST_NUMERIC_TYPE) spec.align_ = ALIGN_NUMERIC; else spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. } // Parse length and convert the argument to the required type. switch (*s++) { case 'h': if (*s == 'h') ArgConverter(arg, *++s).visit(arg); else ArgConverter(arg, *s).visit(arg); break; case 'l': if (*s == 'l') ArgConverter(arg, *++s).visit(arg); else ArgConverter(arg, *s).visit(arg); break; case 'j': ArgConverter(arg, *s).visit(arg); break; case 'z': ArgConverter(arg, *s).visit(arg); break; case 't': ArgConverter(arg, *s).visit(arg); break; case 'L': // printf produces garbage when 'L' is omitted for long double, no // need to do the same. break; default: --s; ArgConverter(arg, *s).visit(arg); } // Parse type. if (!*s) throw FormatError("invalid format string"); if (error_) throw FormatError(error_); spec.type_ = static_cast(*s++); if (arg.type <= Arg::LAST_INTEGER_TYPE) { // Normalize type. switch (spec.type_) { case 'i': case 'u': spec.type_ = 'd'; break; case 'c': // TODO: handle wchar_t CharConverter(arg).visit(arg); break; } } start = s; // Format argument. switch (arg.type) { case Arg::INT: writer.write_int(arg.int_value, spec); break; case Arg::UINT: writer.write_int(arg.uint_value, spec); break; case Arg::LONG_LONG: writer.write_int(arg.long_long_value, spec); break; case Arg::ULONG_LONG: writer.write_int(arg.ulong_long_value, spec); break; case Arg::CHAR: { if (spec.type_ && spec.type_ != 'c') writer.write_int(arg.int_value, spec); typedef typename BasicWriter::CharPtr CharPtr; CharPtr out = CharPtr(); if (spec.width_ > 1) { Char fill = ' '; out = writer.grow_buffer(spec.width_); if (spec.align_ != ALIGN_LEFT) { std::fill_n(out, spec.width_ - 1, fill); out += spec.width_ - 1; } else { std::fill_n(out + 1, spec.width_ - 1, fill); } } else { out = writer.grow_buffer(1); } *out = static_cast(arg.int_value); break; } case Arg::DOUBLE: writer.write_double(arg.double_value, spec); break; case Arg::LONG_DOUBLE: writer.write_double(arg.long_double_value, spec); break; case Arg::STRING: writer.write_str(arg.string, spec); break; case Arg::WSTRING: writer.write_str(ignore_incompatible_str(arg.wstring), spec); break; case Arg::POINTER: if (spec.type_ && spec.type_ != 'p') internal::report_unknown_type(spec.type_, "pointer"); spec.flags_= HASH_FLAG; spec.type_ = 'x'; writer.write_int(reinterpret_cast(arg.pointer_value), spec); break; case Arg::CUSTOM: if (spec.type_) internal::report_unknown_type(spec.type_, "object"); arg.custom.format(&writer, arg.custom.value, "s"); break; default: assert(false); break; } } write(writer, start, s); } template const Char *fmt::BasicFormatter::format( const Char *format_str, const Arg &arg) { const Char *s = format_str; const char *error = 0; FormatSpec spec; if (*s == ':') { if (arg.type == Arg::CUSTOM) { arg.custom.format(this, arg.custom.value, s); return find_closing_brace(s) + 1; } ++s; // Parse fill and alignment. if (Char c = *s) { const Char *p = s + 1; spec.align_ = ALIGN_DEFAULT; do { switch (*p) { case '<': spec.align_ = ALIGN_LEFT; break; case '>': spec.align_ = ALIGN_RIGHT; break; case '=': spec.align_ = ALIGN_NUMERIC; break; case '^': spec.align_ = ALIGN_CENTER; break; } if (spec.align_ != ALIGN_DEFAULT) { if (p != s) { if (c == '}') break; if (c == '{') report_error_(s, "invalid fill character '{'"); s += 2; spec.fill_ = c; } else ++s; if (spec.align_ == ALIGN_NUMERIC && arg.type > Arg::LAST_NUMERIC_TYPE) report_error_(s, "format specifier '=' requires numeric argument"); break; } } while (--p >= s); } // Parse sign. switch (*s) { case '+': check_sign(s, arg); spec.flags_ |= SIGN_FLAG | PLUS_FLAG; break; case '-': check_sign(s, arg); spec.flags_ |= MINUS_FLAG; break; case ' ': check_sign(s, arg); spec.flags_ |= SIGN_FLAG; break; } if (*s == '#') { if (arg.type > Arg::LAST_NUMERIC_TYPE) report_error_(s, "format specifier '#' requires numeric argument"); spec.flags_ |= HASH_FLAG; ++s; } // Parse width and zero flag. if ('0' <= *s && *s <= '9') { if (*s == '0') { if (arg.type > Arg::LAST_NUMERIC_TYPE) report_error_(s, "format specifier '0' requires numeric argument"); spec.align_ = ALIGN_NUMERIC; spec.fill_ = '0'; } // Zero may be parsed again as a part of the width, but it is simpler // and more efficient than checking if the next char is a digit. spec.width_ = parse_nonnegative_int(s, error); if (error) report_error_(s, error); } // Parse precision. if (*s == '.') { ++s; spec.precision_ = 0; if ('0' <= *s && *s <= '9') { spec.precision_ = parse_nonnegative_int(s, error); if (error) report_error_(s, error); } else if (*s == '{') { ++s; ++report_error_.num_open_braces; const Arg &precision_arg = parse_arg_index(s); ULongLong value = 0; switch (precision_arg.type) { case Arg::INT: if (precision_arg.int_value < 0) report_error_(s, "negative precision in format"); value = precision_arg.int_value; break; case Arg::UINT: value = precision_arg.uint_value; break; case Arg::LONG_LONG: if (precision_arg.long_long_value < 0) report_error_(s, "negative precision in format"); value = precision_arg.long_long_value; break; case Arg::ULONG_LONG: value = precision_arg.ulong_long_value; break; default: report_error_(s, "precision is not integer"); } if (value > INT_MAX) report_error_(s, "number is too big in format"); spec.precision_ = static_cast(value); if (*s++ != '}') throw FormatError("unmatched '{' in format"); --report_error_.num_open_braces; } else { report_error_(s, "missing precision in format"); } if (arg.type != Arg::DOUBLE && arg.type != Arg::LONG_DOUBLE) { report_error_(s, "precision specifier requires floating-point argument"); } } // Parse type. if (*s != '}' && *s) spec.type_ = static_cast(*s++); } if (*s++ != '}') throw FormatError("unmatched '{' in format"); start_ = s; // Format argument. internal::ArgFormatter(*this, spec, s - 1).visit(arg); return s; } template void fmt::BasicFormatter::format( BasicStringRef format_str, const ArgList &args) { const Char *s = start_ = format_str.c_str(); args_ = args; next_arg_index_ = 0; while (*s) { Char c = *s++; if (c != '{' && c != '}') continue; if (*s == c) { write(writer_, start_, s); start_ = ++s; continue; } if (c == '}') throw FormatError("unmatched '}' in format"); report_error_.num_open_braces = 1; write(writer_, start_, s - 1); Arg arg = parse_arg_index(s); s = format(s, arg); } write(writer_, start_, s); } void fmt::report_system_error( int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { // FIXME: format_system_error may throw report_error(internal::format_system_error, error_code, message); } #ifdef _WIN32 void fmt::report_windows_error( int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { // FIXME: format_windows_error may throw report_error(internal::format_windows_error, error_code, message); } #endif void fmt::print(std::FILE *f, StringRef format_str, const ArgList &args) { Writer w; w.write(format_str, args); std::fwrite(w.data(), 1, w.size(), f); } void fmt::print(std::ostream &os, StringRef format_str, const ArgList &args) { Writer w; w.write(format_str, args); os.write(w.data(), w.size()); } void fmt::print_colored(Color c, StringRef format, const ArgList &args) { char escape[] = "\x1b[30m"; escape[3] = '0' + static_cast(c); std::fputs(escape, stdout); print(format, args); std::fputs(RESET_COLOR, stdout); } int fmt::fprintf(std::FILE *f, StringRef format, const ArgList &args) { Writer w; printf(w, format, args); return std::fwrite(w.data(), 1, w.size(), f); } // Explicit instantiations for char. template fmt::BasicWriter::CharPtr fmt::BasicWriter::fill_padding(CharPtr buffer, unsigned total_size, std::size_t content_size, wchar_t fill); template void fmt::BasicFormatter::format( BasicStringRef format, const ArgList &args); template void fmt::internal::PrintfFormatter::format( BasicWriter &writer, BasicStringRef format, const ArgList &args); // Explicit instantiations for wchar_t. template fmt::BasicWriter::CharPtr fmt::BasicWriter::fill_padding(CharPtr buffer, unsigned total_size, std::size_t content_size, wchar_t fill); template void fmt::BasicFormatter::format( BasicStringRef format, const ArgList &args); template void fmt::internal::PrintfFormatter::format( BasicWriter &writer, BasicStringRef format, const ArgList &args); #ifdef _MSC_VER # pragma warning(pop) #endif Index: ps/trunk/source/third_party/cppformat/format.h =================================================================== --- ps/trunk/source/third_party/cppformat/format.h (revision 23415) +++ ps/trunk/source/third_party/cppformat/format.h (revision 23416) @@ -1,2083 +1,2083 @@ /* * Slightly modified version of cppformat, by Wildfire Games, for 0 A.D. * Based on cppformat v0.11.0 from https://github.com/cppformat/cppformat */ /* Formatting library for C++ Copyright (c) 2012 - 2014, Victor Zverovich All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ #include #include #include // for std::ptrdiff_t #include #include #include #include #include #include #if defined(_SECURE_SCL) && _SECURE_SCL # include #endif #ifdef __GNUC__ # define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) # define FMT_GCC_EXTENSION __extension__ // Disable warning about "long long" which is sometimes reported even // when using __extension__. # if FMT_GCC_VERSION >= 406 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wlong-long" # endif #else # define FMT_GCC_EXTENSION #endif #ifdef __GNUC_LIBSTD__ # define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) #endif #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) #else # define FMT_HAS_FEATURE(x) 0 #endif #ifdef __has_builtin # define FMT_HAS_BUILTIN(x) __has_builtin(x) #else # define FMT_HAS_BUILTIN(x) 0 #endif #ifndef FMT_USE_VARIADIC_TEMPLATES // Variadic templates are available in GCC since version 4.4 // (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++ // since version 2013. -# if defined(_MSC_VER) && _MSC_VER >= 1800 +# if defined(_MSC_VER) # define FMT_USE_VARIADIC_TEMPLATES 1 # else # define FMT_USE_VARIADIC_TEMPLATES \ (FMT_HAS_FEATURE(cxx_variadic_templates) || \ (FMT_GCC_VERSION >= 404 && __cplusplus >= 201103)) # endif #endif #ifndef FMT_USE_RVALUE_REFERENCES // Don't use rvalue references when compiling with clang and an old libstdc++ // as the latter doesn't provide std::move. # if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402 # define FMT_USE_RVALUE_REFERENCES 0 -# elif defined(_MSC_VER) && _MSC_VER >= 1600 +# elif defined(_MSC_VER) # define FMT_USE_RVALUE_REFERENCES 1 # else # define FMT_USE_RVALUE_REFERENCES \ (FMT_HAS_FEATURE(cxx_rvalue_references) || \ (FMT_GCC_VERSION >= 403 && __cplusplus >= 201103)) # endif #endif #if FMT_USE_RVALUE_REFERENCES # include // for std::move #endif // Define FMT_USE_NOEXCEPT to make C++ Format use noexcept (C++11 feature). #if (defined(FMT_USE_NOEXCEPT) && FMT_USE_NOEXCEPT) || FMT_HAS_FEATURE(cxx_noexcept) || \ (FMT_GCC_VERSION >= 408 && __cplusplus >= 201103) # define FMT_NOEXCEPT(expr) noexcept(expr) #else # define FMT_NOEXCEPT(expr) #endif // A macro to disallow the copy constructor and operator= functions // This should be used in the private: declarations for a class #define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&); \ void operator=(const TypeName&) namespace fmt { // Fix the warning about long long on older versions of GCC // that don't support the diagnostic pragma. FMT_GCC_EXTENSION typedef long long LongLong; FMT_GCC_EXTENSION typedef unsigned long long ULongLong; #if FMT_USE_RVALUE_REFERENCES using std::move; #endif template class BasicWriter; typedef BasicWriter Writer; typedef BasicWriter WWriter; template class BasicFormatter; template void format(BasicFormatter &f, const Char *format_str, const T &value); /** \rst A string reference. It can be constructed from a C string or ``std::string``. You can use one of the following typedefs for common character types: +------------+-------------------------+ | Type | Definition | +============+=========================+ | StringRef | BasicStringRef | +------------+-------------------------+ | WStringRef | BasicStringRef | +------------+-------------------------+ This class is most useful as a parameter type to allow passing different types of strings to a function, for example:: template std::string format(StringRef format, const Args & ... args); format("{}", 42); format(std::string("{}"), 42); \endrst */ template class BasicStringRef { private: const Char *data_; mutable std::size_t size_; public: /** Constructs a string reference object from a C string and a size. If *size* is zero, which is the default, the size is computed automatically. */ BasicStringRef(const Char *s, std::size_t size = 0) : data_(s), size_(size) {} /** Constructs a string reference from an `std::string` object. */ BasicStringRef(const std::basic_string &s) : data_(s.c_str()), size_(s.size()) {} /** Converts a string reference to an `std::string` object. */ operator std::basic_string() const { return std::basic_string(data_, size()); } /** Returns the pointer to a C string. */ const Char *c_str() const { return data_; } /** Returns the string size. */ std::size_t size() const { if (size_ == 0 && data_) size_ = std::char_traits::length(data_); return size_; } friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) { return lhs.data_ == rhs.data_; } friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) { return lhs.data_ != rhs.data_; } }; typedef BasicStringRef StringRef; typedef BasicStringRef WStringRef; /** A formatting error such as invalid format string. */ class FormatError : public std::runtime_error { public: explicit FormatError(const std::string &message) : std::runtime_error(message) {} }; namespace internal { // The number of characters to store in the Array object, representing the // output buffer, itself to avoid dynamic memory allocation. enum { INLINE_BUFFER_SIZE = 500 }; #if defined(_SECURE_SCL) && _SECURE_SCL // Use checked iterator to avoid warnings on MSVC. template inline stdext::checked_array_iterator make_ptr(T *ptr, std::size_t size) { return stdext::checked_array_iterator(ptr, size); } #else template inline T *make_ptr(T *ptr, std::size_t) { return ptr; } #endif // A simple array for POD types with the first SIZE elements stored in // the object itself. It supports a subset of std::vector's operations. template class Array { private: std::size_t size_; std::size_t capacity_; T *ptr_; T data_[SIZE]; void grow(std::size_t size); // Free memory allocated by the array. void free() { if (ptr_ != data_) delete [] ptr_; } // Move data from other to this array. void move(Array &other) { size_ = other.size_; capacity_ = other.capacity_; if (other.ptr_ == other.data_) { ptr_ = data_; std::copy(other.data_, other.data_ + size_, make_ptr(data_, capacity_)); } else { ptr_ = other.ptr_; // Set pointer to the inline array so that delete is not called // when freeing. other.ptr_ = other.data_; } } FMT_DISALLOW_COPY_AND_ASSIGN(Array); public: explicit Array(std::size_t size = 0) : size_(size), capacity_(SIZE), ptr_(data_) {} ~Array() { free(); } #if FMT_USE_RVALUE_REFERENCES Array(Array &&other) { move(other); } Array& operator=(Array &&other) { assert(this != &other); free(); move(other); return *this; } #endif // Returns the size of this array. std::size_t size() const { return size_; } // Returns the capacity of this array. std::size_t capacity() const { return capacity_; } // Resizes the array. If T is a POD type new elements are not initialized. void resize(std::size_t new_size) { if (new_size > capacity_) grow(new_size); size_ = new_size; } // Reserves space to store at least capacity elements. void reserve(std::size_t capacity) { if (capacity > capacity_) grow(capacity); } void clear() { size_ = 0; } void push_back(const T &value) { if (size_ == capacity_) grow(size_ + 1); ptr_[size_++] = value; } // Appends data to the end of the array. void append(const T *begin, const T *end); T &operator[](std::size_t index) { return ptr_[index]; } const T &operator[](std::size_t index) const { return ptr_[index]; } }; template void Array::grow(std::size_t size) { capacity_ = (std::max)(size, capacity_ + capacity_ / 2); T *p = new T[capacity_]; std::copy(ptr_, ptr_ + size_, make_ptr(p, capacity_)); if (ptr_ != data_) delete [] ptr_; ptr_ = p; } template void Array::append(const T *begin, const T *end) { std::ptrdiff_t num_elements = end - begin; if (size_ + num_elements > capacity_) grow(size_ + num_elements); std::copy(begin, end, make_ptr(ptr_, capacity_) + size_); size_ += num_elements; } template class BasicCharTraits { public: #if defined(_SECURE_SCL) && _SECURE_SCL typedef stdext::checked_array_iterator CharPtr; #else typedef Char *CharPtr; #endif }; template class CharTraits; template <> class CharTraits : public BasicCharTraits { private: // Conversion from wchar_t to char is not allowed. static char convert(wchar_t); public: typedef const wchar_t *UnsupportedStrType; static char convert(char value) { return value; } // Formats a floating-point number. template static int format_float(char *buffer, std::size_t size, const char *format, unsigned width, int precision, T value); }; template <> class CharTraits : public BasicCharTraits { public: typedef const char *UnsupportedStrType; static wchar_t convert(char value) { return value; } static wchar_t convert(wchar_t value) { return value; } template static int format_float(wchar_t *buffer, std::size_t size, const wchar_t *format, unsigned width, int precision, T value); }; // Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. template struct TypeSelector { typedef uint32_t Type; }; template <> struct TypeSelector { typedef uint64_t Type; }; // Checks if a number is negative - used to avoid warnings. template struct SignChecker { template static bool is_negative(T) { return false; } }; template <> struct SignChecker { template static bool is_negative(T value) { return value < 0; } }; // Returns true if value is negative, false otherwise. // Same as (value < 0) but doesn't produce warnings if T is an unsigned type. template inline bool is_negative(T value) { return SignChecker::is_signed>::is_negative(value); } template struct IntTraits { // Smallest of uint32_t and uint64_t that is large enough to represent // all values of T. typedef typename TypeSelector::digits <= 32>::Type MainType; }; // MakeUnsigned::Type gives an unsigned type corresponding to integer type T. template struct MakeUnsigned { typedef T Type; }; #define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \ template <> \ struct MakeUnsigned { typedef U Type; } FMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char); FMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char); FMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short); FMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned); FMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long); FMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong); void report_unknown_type(char code, const char *type); extern const uint32_t POWERS_OF_10_32[]; extern const uint64_t POWERS_OF_10_64[]; #if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) // Returns the number of decimal digits in n. Leading zeros are not counted // except for n == 0 in which case count_digits returns 1. inline unsigned count_digits(uint64_t n) { // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. unsigned t = (64 - __builtin_clzll(n | 1)) * 1233 >> 12; return t - (n < POWERS_OF_10_64[t]) + 1; } # if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) // Optional version of count_digits for better performance on 32-bit platforms. inline unsigned count_digits(uint32_t n) { uint32_t t = (32 - __builtin_clz(n | 1)) * 1233 >> 12; return t - (n < POWERS_OF_10_32[t]) + 1; } # endif #else // Slower version of count_digits used when __builtin_clz is not available. inline unsigned count_digits(uint64_t n) { unsigned count = 1; for (;;) { // Integer division is slow so do it for a group of four digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. if (n < 10) return count; if (n < 100) return count + 1; if (n < 1000) return count + 2; if (n < 10000) return count + 3; n /= 10000u; count += 4; } } #endif extern const char DIGITS[]; // Formats a decimal unsigned integer value writing into buffer. template inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { --num_digits; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. unsigned index = (value % 100) * 2; value /= 100; buffer[num_digits] = DIGITS[index + 1]; buffer[num_digits - 1] = DIGITS[index]; num_digits -= 2; } if (value < 10) { *buffer = static_cast('0' + value); return; } unsigned index = static_cast(value * 2); buffer[1] = DIGITS[index + 1]; buffer[0] = DIGITS[index]; } #ifdef _WIN32 // A converter from UTF-8 to UTF-16. // It is only provided for Windows since other systems use UTF-8. class UTF8ToUTF16 { private: Array buffer_; public: explicit UTF8ToUTF16(StringRef s); operator WStringRef() const { return WStringRef(&buffer_[0], size()); } size_t size() const { return buffer_.size() - 1; } const wchar_t *c_str() const { return &buffer_[0]; } std::wstring str() const { return std::wstring(&buffer_[0], size()); } }; // A converter from UTF-16 to UTF-8. // It is only provided for Windows since other systems use UTF-8. class UTF16ToUTF8 { private: Array buffer_; public: UTF16ToUTF8() {} explicit UTF16ToUTF8(WStringRef s); operator StringRef() const { return StringRef(&buffer_[0], size()); } size_t size() const { return buffer_.size() - 1; } const char *c_str() const { return &buffer_[0]; } std::string str() const { return std::string(&buffer_[0], size()); } // Performs conversion returning a system error code instead of // throwing exception on error. int convert(WStringRef s); }; #endif // Portable thread-safe version of strerror. // Sets buffer to point to a string describing the error code. // This can be either a pointer to a string stored in buffer, // or a pointer to some static immutable string. // Returns one of the following values: // 0 - success // ERANGE - buffer is not large enough to store the error message // other - failure // Buffer should be at least of size 1. int safe_strerror(int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT(true); void format_system_error( fmt::Writer &out, int error_code, fmt::StringRef message); #ifdef _WIN32 void format_windows_error( fmt::Writer &out, int error_code, fmt::StringRef message); #endif // Throws Exception(message) if format contains '}', otherwise throws // FormatError reporting unmatched '{'. The idea is that unmatched '{' // should override other errors. template struct FormatErrorReporter { int num_open_braces; void operator()(const Char *s, fmt::StringRef message) const; }; // Computes max(Arg, 1) at compile time. It is used to avoid errors about // allocating an array of 0 size. template struct NonZero { enum { VALUE = Arg }; }; template <> struct NonZero<0> { enum { VALUE = 1 }; }; // A formatting argument. It is a POD type to allow storage in internal::Array. struct Arg { enum Type { // Integer types should go first, INT, UINT, LONG_LONG, ULONG_LONG, CHAR, LAST_INTEGER_TYPE = CHAR, // followed by floating-point types. DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE, STRING, WSTRING, POINTER, CUSTOM }; Type type; template struct StringValue { const Char *value; std::size_t size; }; typedef void (*FormatFunc)( void *formatter, const void *arg, const void *format_str); struct CustomValue { const void *value; FormatFunc format; }; union { int int_value; unsigned uint_value; LongLong long_long_value; ULongLong ulong_long_value; double double_value; long double long_double_value; const void *pointer_value; StringValue string; StringValue wstring; CustomValue custom; }; }; // Makes an Arg object from any type. template class MakeArg : public Arg { private: // The following two methods are private to disallow formatting of // arbitrary pointers. If you want to output a pointer cast it to // "void *" or "const void *". In particular, this forbids formatting // of "[const] volatile char *" which is printed as bool by iostreams. // Do not implement! template MakeArg(const T *value); template MakeArg(T *value); void set_string(StringRef str) { type = STRING; string.value = str.c_str(); string.size = str.size(); } void set_string(WStringRef str) { type = WSTRING; CharTraits::convert(wchar_t()); wstring.value = str.c_str(); wstring.size = str.size(); } // Formats an argument of a custom type, such as a user-defined class. template static void format_custom_arg( void *formatter, const void *arg, const void *format_str) { format(*static_cast*>(formatter), static_cast(format_str), *static_cast(arg)); } public: MakeArg() {} MakeArg(bool value) { type = INT; int_value = value; } MakeArg(short value) { type = INT; int_value = value; } MakeArg(unsigned short value) { type = UINT; uint_value = value; } MakeArg(int value) { type = INT; int_value = value; } MakeArg(unsigned value) { type = UINT; uint_value = value; } MakeArg(long value) { // To minimize the number of types we need to deal with, long is // translated either to int or to long long depending on its size. if (sizeof(long) == sizeof(int)) { type = INT; int_value = static_cast(value); } else { type = LONG_LONG; long_long_value = value; } } MakeArg(unsigned long value) { if (sizeof(unsigned long) == sizeof(unsigned)) { type = UINT; uint_value = static_cast(value); } else { type = ULONG_LONG; ulong_long_value = value; } } MakeArg(LongLong value) { type = LONG_LONG; long_long_value = value; } MakeArg(ULongLong value) { type = ULONG_LONG; ulong_long_value = value; } MakeArg(float value) { type = DOUBLE; double_value = value; } MakeArg(double value) { type = DOUBLE; double_value = value; } MakeArg(long double value) { type = LONG_DOUBLE; long_double_value = value; } MakeArg(signed char value) { type = CHAR; int_value = value; } MakeArg(unsigned char value) { type = CHAR; int_value = value; } MakeArg(char value) { type = CHAR; int_value = value; } MakeArg(wchar_t value) { type = CHAR; int_value = internal::CharTraits::convert(value); } MakeArg(char *value) { set_string(value); } MakeArg(const char *value) { set_string(value); } MakeArg(const std::string &value) { set_string(value); } MakeArg(StringRef value) { set_string(value); } MakeArg(wchar_t *value) { set_string(value); } MakeArg(const wchar_t *value) { set_string(value); } MakeArg(const std::wstring &value) { set_string(value); } MakeArg(WStringRef value) { set_string(value); } MakeArg(void *value) { type = POINTER; pointer_value = value; } MakeArg(const void *value) { type = POINTER; pointer_value = value; } #if 0 // WFG: Removed this because otherwise you can pass a CStr8 or an enum etc // into fmt::sprintf, and it will be interpreted as a CUSTOM type and then // will throw an exception at runtime, which is terrible behaviour. template MakeArg(const T &value) { type = CUSTOM; custom.value = &value; custom.format = &format_custom_arg; } #endif }; #define FMT_DISPATCH(call) static_cast(this)->call // An argument visitor. // To use ArgVisitor define a subclass that implements some or all of the // visit methods with the same signatures as the methods in ArgVisitor, // for example, visit_int(int). // Specify the subclass name as the Impl template parameter. Then calling // ArgVisitor::visit for some argument will dispatch to a visit method // specific to the argument type. For example, if the argument type is // double then visit_double(double) method of a subclass will be called. // If the subclass doesn't contain a method with this signature, then // a corresponding method of ArgVisitor will be called. // // Example: // class MyArgVisitor : public ArgVisitor { // public: // void visit_int(int value) { print("{}", value); } // void visit_double(double value) { print("{}", value ); } // }; // // ArgVisitor uses the curiously recurring template pattern: // http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern template class ArgVisitor { public: Result visit_unhandled_arg() { return Result(); } Result visit_int(int value) { return FMT_DISPATCH(visit_any_int(value)); } Result visit_long_long(LongLong value) { return FMT_DISPATCH(visit_any_int(value)); } Result visit_uint(unsigned value) { return FMT_DISPATCH(visit_any_int(value)); } Result visit_ulong_long(ULongLong value) { return FMT_DISPATCH(visit_any_int(value)); } Result visit_char(int value) { return FMT_DISPATCH(visit_any_int(value)); } template Result visit_any_int(T) { return FMT_DISPATCH(visit_unhandled_arg()); } Result visit_double(double value) { return FMT_DISPATCH(visit_any_double(value)); } Result visit_long_double(long double value) { return FMT_DISPATCH(visit_any_double(value)); } template Result visit_any_double(T) { return FMT_DISPATCH(visit_unhandled_arg()); } Result visit_string(Arg::StringValue) { return FMT_DISPATCH(visit_unhandled_arg()); } Result visit_wstring(Arg::StringValue) { return FMT_DISPATCH(visit_unhandled_arg()); } Result visit_pointer(const void *) { return FMT_DISPATCH(visit_unhandled_arg()); } Result visit_custom(Arg::CustomValue) { return FMT_DISPATCH(visit_unhandled_arg()); } Result visit(const Arg &arg) { switch (arg.type) { default: assert(false); // Fall through. case Arg::INT: return FMT_DISPATCH(visit_int(arg.int_value)); case Arg::UINT: return FMT_DISPATCH(visit_uint(arg.uint_value)); case Arg::LONG_LONG: return FMT_DISPATCH(visit_long_long(arg.long_long_value)); case Arg::ULONG_LONG: return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value)); case Arg::DOUBLE: return FMT_DISPATCH(visit_double(arg.double_value)); case Arg::LONG_DOUBLE: return FMT_DISPATCH(visit_long_double(arg.long_double_value)); case Arg::CHAR: return FMT_DISPATCH(visit_char(arg.int_value)); case Arg::STRING: return FMT_DISPATCH(visit_string(arg.string)); case Arg::WSTRING: return FMT_DISPATCH(visit_wstring(arg.wstring)); case Arg::POINTER: return FMT_DISPATCH(visit_pointer(arg.pointer_value)); case Arg::CUSTOM: return FMT_DISPATCH(visit_custom(arg.custom)); } } }; class RuntimeError : public std::runtime_error { protected: RuntimeError() : std::runtime_error("") {} }; template class ArgFormatter; } // namespace internal /** An argument list. */ class ArgList { private: const internal::Arg *args_; std::size_t size_; public: ArgList() : size_(0) {} ArgList(const internal::Arg *args, std::size_t size) : args_(args), size_(size) {} /** Returns the list size (the number of arguments). */ std::size_t size() const { return size_; } /** Returns the argument at specified index. */ const internal::Arg &operator[](std::size_t index) const { return args_[index]; } }; struct FormatSpec; namespace internal { class FormatterBase { protected: ArgList args_; int next_arg_index_; const char *error_; FormatterBase() : error_(0) {} const Arg &next_arg(); const Arg &handle_arg_index(unsigned arg_index); template void write(BasicWriter &w, const Char *start, const Char *end) { if (start != end) w << BasicStringRef(start, end - start); } // TODO }; // A printf formatter. template class PrintfFormatter : private FormatterBase { private: void parse_flags(FormatSpec &spec, const Char *&s); // Parses argument index, flags and width and returns the parsed // argument index. unsigned parse_header(const Char *&s, FormatSpec &spec); public: void format(BasicWriter &writer, BasicStringRef format, const ArgList &args); }; } // namespace internal // A formatter. template class BasicFormatter : private internal::FormatterBase { private: BasicWriter &writer_; const Char *start_; internal::FormatErrorReporter report_error_; // Parses argument index and returns an argument with this index. const internal::Arg &parse_arg_index(const Char *&s); void check_sign(const Char *&s, const internal::Arg &arg); public: explicit BasicFormatter(BasicWriter &w) : writer_(w) {} BasicWriter &writer() { return writer_; } void format(BasicStringRef format_str, const ArgList &args); const Char *format(const Char *format_str, const internal::Arg &arg); }; enum Alignment { ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC }; // Flags. enum { SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8, CHAR_FLAG = 0x10 // Argument has char type - used in error reporting. }; // An empty format specifier. struct EmptySpec {}; // A type specifier. template struct TypeSpec : EmptySpec { Alignment align() const { return ALIGN_DEFAULT; } unsigned width() const { return 0; } int precision() const { return -1; } bool flag(unsigned) const { return false; } char type() const { return TYPE; } char fill() const { return ' '; } }; // A width specifier. struct WidthSpec { unsigned width_; // Fill is always wchar_t and cast to char if necessary to avoid having // two specialization of WidthSpec and its subclasses. wchar_t fill_; WidthSpec(unsigned width, wchar_t fill) : width_(width), fill_(fill) {} unsigned width() const { return width_; } wchar_t fill() const { return fill_; } }; // An alignment specifier. struct AlignSpec : WidthSpec { Alignment align_; AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT) : WidthSpec(width, fill), align_(align) {} Alignment align() const { return align_; } int precision() const { return -1; } }; // An alignment and type specifier. template struct AlignTypeSpec : AlignSpec { AlignTypeSpec(unsigned width, wchar_t fill) : AlignSpec(width, fill) {} bool flag(unsigned) const { return false; } char type() const { return TYPE; } }; // A full format specifier. struct FormatSpec : AlignSpec { unsigned flags_; int precision_; char type_; FormatSpec( unsigned width = 0, char type = 0, wchar_t fill = ' ') : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) {} bool flag(unsigned f) const { return (flags_ & f) != 0; } int precision() const { return precision_; } char type() const { return type_; } }; // An integer format specifier. template , typename Char = char> class IntFormatSpec : public SpecT { private: T value_; public: IntFormatSpec(T value, const SpecT &spec = SpecT()) : SpecT(spec), value_(value) {} T value() const { return value_; } }; // A string format specifier. template class StrFormatSpec : public AlignSpec { private: const T *str_; public: StrFormatSpec(const T *str, unsigned width, wchar_t fill) : AlignSpec(width, fill), str_(str) {} const T *str() const { return str_; } }; /** Returns an integer format specifier to format the value in base 2. */ IntFormatSpec > bin(int value); /** Returns an integer format specifier to format the value in base 8. */ IntFormatSpec > oct(int value); /** Returns an integer format specifier to format the value in base 16 using lower-case letters for the digits above 9. */ IntFormatSpec > hex(int value); /** Returns an integer formatter format specifier to format in base 16 using upper-case letters for the digits above 9. */ IntFormatSpec > hexu(int value); /** \rst Returns an integer format specifier to pad the formatted argument with the fill character to the specified width using the default (right) numeric alignment. **Example**:: Writer out; out << pad(hex(0xcafe), 8, '0'); // out.str() == "0000cafe" \endrst */ template IntFormatSpec, Char> pad( int value, unsigned width, Char fill = ' '); #define FMT_DEFINE_INT_FORMATTERS(TYPE) \ inline IntFormatSpec > bin(TYPE value) { \ return IntFormatSpec >(value, TypeSpec<'b'>()); \ } \ \ inline IntFormatSpec > oct(TYPE value) { \ return IntFormatSpec >(value, TypeSpec<'o'>()); \ } \ \ inline IntFormatSpec > hex(TYPE value) { \ return IntFormatSpec >(value, TypeSpec<'x'>()); \ } \ \ inline IntFormatSpec > hexu(TYPE value) { \ return IntFormatSpec >(value, TypeSpec<'X'>()); \ } \ \ template \ inline IntFormatSpec > pad( \ IntFormatSpec > f, unsigned width) { \ return IntFormatSpec >( \ f.value(), AlignTypeSpec(width, ' ')); \ } \ \ /* For compatibility with older compilers we provide two overloads for pad, */ \ /* one that takes a fill character and one that doesn't. In the future this */ \ /* can be replaced with one overload making the template argument Char */ \ /* default to char (C++11). */ \ template \ inline IntFormatSpec, Char> pad( \ IntFormatSpec, Char> f, \ unsigned width, Char fill) { \ return IntFormatSpec, Char>( \ f.value(), AlignTypeSpec(width, fill)); \ } \ \ inline IntFormatSpec > pad( \ TYPE value, unsigned width) { \ return IntFormatSpec >( \ value, AlignTypeSpec<0>(width, ' ')); \ } \ \ template \ inline IntFormatSpec, Char> pad( \ TYPE value, unsigned width, Char fill) { \ return IntFormatSpec, Char>( \ value, AlignTypeSpec<0>(width, fill)); \ } FMT_DEFINE_INT_FORMATTERS(int) FMT_DEFINE_INT_FORMATTERS(long) FMT_DEFINE_INT_FORMATTERS(unsigned) FMT_DEFINE_INT_FORMATTERS(unsigned long) FMT_DEFINE_INT_FORMATTERS(LongLong) FMT_DEFINE_INT_FORMATTERS(ULongLong) /** \rst Returns a string formatter that pads the formatted argument with the fill character to the specified width using the default (left) string alignment. **Example**:: std::string s = str(Writer() << pad("abc", 8)); // s == "abc " \endrst */ template inline StrFormatSpec pad( const Char *str, unsigned width, Char fill = ' ') { return StrFormatSpec(str, width, fill); } inline StrFormatSpec pad( const wchar_t *str, unsigned width, char fill = ' ') { return StrFormatSpec(str, width, fill); } // Generates a comma-separated list with results of applying f to numbers 0..n-1. # define FMT_GEN(n, f) FMT_GEN##n(f) # define FMT_GEN1(f) f(0) # define FMT_GEN2(f) FMT_GEN1(f), f(1) # define FMT_GEN3(f) FMT_GEN2(f), f(2) # define FMT_GEN4(f) FMT_GEN3(f), f(3) # define FMT_GEN5(f) FMT_GEN4(f), f(4) # define FMT_GEN6(f) FMT_GEN5(f), f(5) # define FMT_GEN7(f) FMT_GEN6(f), f(6) # define FMT_GEN8(f) FMT_GEN7(f), f(7) # define FMT_GEN9(f) FMT_GEN8(f), f(8) # define FMT_GEN10(f) FMT_GEN9(f), f(9) # define FMT_MAKE_TEMPLATE_ARG(n) typename T##n # define FMT_MAKE_ARG(n) const T##n &v##n # define FMT_MAKE_REF_char(n) fmt::internal::MakeArg(v##n) # define FMT_MAKE_REF_wchar_t(n) fmt::internal::MakeArg(v##n) #if FMT_USE_VARIADIC_TEMPLATES // Defines a variadic function returning void. # define FMT_VARIADIC_VOID(func, arg_type) \ template \ void func(arg_type arg1, const Args & ... args) { \ using fmt::internal::Arg; \ const Arg arg_array[fmt::internal::NonZero::VALUE] = { \ fmt::internal::MakeArg(args)... \ }; \ func(arg1, ArgList(arg_array, sizeof...(Args))); \ } // Defines a variadic constructor. # define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ template \ ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \ using fmt::internal::Arg; \ const Arg arg_array[fmt::internal::NonZero::VALUE] = { \ fmt::internal::MakeArg(args)... \ }; \ func(arg0, arg1, ArgList(arg_array, sizeof...(Args))); \ } #else # define FMT_MAKE_REF(n) fmt::internal::MakeArg(v##n) // Defines a wrapper for a function taking one argument of type arg_type // and n additional arguments of arbitrary types. # define FMT_WRAP1(func, arg_type, n) \ template \ inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ const fmt::internal::Arg args[] = {FMT_GEN(n, FMT_MAKE_REF)}; \ func(arg1, fmt::ArgList(args, sizeof(args) / sizeof(*args))); \ } // Emulates a variadic function returning void on a pre-C++11 compiler. # define FMT_VARIADIC_VOID(func, arg_type) \ FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \ FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \ FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \ FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \ FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10) # define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \ template \ ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ const fmt::internal::Arg args[] = {FMT_GEN(n, FMT_MAKE_REF)}; \ func(arg0, arg1, fmt::ArgList(args, sizeof(args) / sizeof(*args))); \ } // Emulates a variadic constructor on a pre-C++11 compiler. # define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \ FMT_CTOR(ctor, func, arg0_type, arg1_type, 10) #endif // Generates a comma-separated list with results of applying f to pairs // (argument, index). #define FMT_FOR_EACH1(f, x0) f(x0, 0) #define FMT_FOR_EACH2(f, x0, x1) \ FMT_FOR_EACH1(f, x0), f(x1, 1) #define FMT_FOR_EACH3(f, x0, x1, x2) \ FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2) #define FMT_FOR_EACH4(f, x0, x1, x2, x3) \ FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3) #define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \ FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4) #define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \ FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5) #define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \ FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6) #define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \ FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7) #define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \ FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8) #define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \ FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9) /** An error returned by an operating system or a language runtime, for example a file opening error. */ class SystemError : public internal::RuntimeError { private: void init(int error_code, StringRef format_str, const ArgList &args); protected: int error_code_; typedef char Char; // For FMT_VARIADIC_CTOR. SystemError() {} public: /** \rst Constructs a :cpp:class:`fmt::SystemError` object with the description of the form "**: **", where ** is the formatted message and ** is the system message corresponding to the error code. *error_code* is a system error code as given by ``errno``. \endrst */ SystemError(int error_code, StringRef message) { init(error_code, message, ArgList()); } FMT_VARIADIC_CTOR(SystemError, init, int, StringRef) int error_code() const { return error_code_; } }; /** \rst This template provides operations for formatting and writing data into a character stream. The output is stored in a memory buffer that grows dynamically. You can use one of the following typedefs for common character types: +---------+----------------------+ | Type | Definition | +=========+======================+ | Writer | BasicWriter | +---------+----------------------+ | WWriter | BasicWriter | +---------+----------------------+ **Example**:: Writer out; out << "The answer is " << 42 << "\n"; out.write("({:+f}, {:+f})", -3.14, 3.14); This will write the following output to the ``out`` object: .. code-block:: none The answer is 42 (-3.140000, +3.140000) The output can be converted to an ``std::string`` with ``out.str()`` or accessed as a C string with ``out.c_str()``. \endrst */ template class BasicWriter { private: // Output buffer. mutable internal::Array buffer_; typedef typename internal::CharTraits::CharPtr CharPtr; #if defined(_SECURE_SCL) && _SECURE_SCL // Returns pointer value. static Char *get(CharPtr p) { return p.base(); } #else static Char *get(Char *p) { return p; } #endif static CharPtr fill_padding(CharPtr buffer, unsigned total_size, std::size_t content_size, wchar_t fill); // Grows the buffer by n characters and returns a pointer to the newly // allocated area. CharPtr grow_buffer(std::size_t n) { std::size_t size = buffer_.size(); buffer_.resize(size + n); return internal::make_ptr(&buffer_[size], n); } // Prepare a buffer for integer formatting. CharPtr prepare_int_buffer(unsigned num_digits, const EmptySpec &, const char *prefix, unsigned prefix_size) { unsigned size = prefix_size + num_digits; CharPtr p = grow_buffer(size); std::copy(prefix, prefix + prefix_size, p); return p + size - 1; } template CharPtr prepare_int_buffer(unsigned num_digits, const Spec &spec, const char *prefix, unsigned prefix_size); // Formats an integer. template void write_int(T value, const Spec &spec); // Formats a floating-point number (double or long double). template void write_double(T value, const FormatSpec &spec); // Writes a formatted string. template CharPtr write_str( const StrChar *s, std::size_t size, const AlignSpec &spec); template void write_str( const internal::Arg::StringValue &str, const FormatSpec &spec); // This method is private to disallow writing a wide string to a // char stream and vice versa. If you want to print a wide string // as a pointer as std::ostream does, cast it to const void*. // Do not implement! void operator<<(typename internal::CharTraits::UnsupportedStrType); friend class internal::ArgFormatter; friend class internal::PrintfFormatter; public: /** Constructs a ``BasicWriter`` object. */ BasicWriter() {} #if FMT_USE_RVALUE_REFERENCES /** Constructs a ``BasicWriter`` object moving the content of the other object to it. */ BasicWriter(BasicWriter &&other) : buffer_(std::move(other.buffer_)) {} /** Moves the content of the other ``BasicWriter`` object to this one. */ BasicWriter& operator=(BasicWriter &&other) { assert(this != &other); buffer_ = std::move(other.buffer_); return *this; } #endif /** Returns the total number of characters written. */ std::size_t size() const { return buffer_.size(); } /** Returns a pointer to the output buffer content. No terminating null character is appended. */ const Char *data() const { return &buffer_[0]; } /** Returns a pointer to the output buffer content with terminating null character appended. */ const Char *c_str() const { std::size_t size = buffer_.size(); buffer_.reserve(size + 1); buffer_[size] = '\0'; return &buffer_[0]; } /** Returns the content of the output buffer as an `std::string`. */ std::basic_string str() const { return std::basic_string(&buffer_[0], buffer_.size()); } /** \rst Writes formatted data. *args* is an argument list representing arbitrary arguments. **Example**:: Writer out; out.write("Current point:\n"); out.write("({:+f}, {:+f})", -3.14, 3.14); This will write the following output to the ``out`` object: .. code-block:: none Current point: (-3.140000, +3.140000) The output can be accessed using :meth:`data`, :meth:`c_str` or :meth:`str` methods. See also `Format String Syntax`_. \endrst */ void write(BasicStringRef format, const ArgList &args) { BasicFormatter(*this).format(format, args); } FMT_VARIADIC_VOID(write, fmt::BasicStringRef) BasicWriter &operator<<(int value) { return *this << IntFormatSpec(value); } BasicWriter &operator<<(unsigned value) { return *this << IntFormatSpec(value); } BasicWriter &operator<<(long value) { return *this << IntFormatSpec(value); } BasicWriter &operator<<(unsigned long value) { return *this << IntFormatSpec(value); } BasicWriter &operator<<(LongLong value) { return *this << IntFormatSpec(value); } /** Formats *value* and writes it to the stream. */ BasicWriter &operator<<(ULongLong value) { return *this << IntFormatSpec(value); } BasicWriter &operator<<(double value) { write_double(value, FormatSpec()); return *this; } /** Formats *value* using the general format for floating-point numbers (``'g'``) and writes it to the stream. */ BasicWriter &operator<<(long double value) { write_double(value, FormatSpec()); return *this; } /** Writes a character to the stream. */ BasicWriter &operator<<(char value) { buffer_.push_back(value); return *this; } BasicWriter &operator<<(wchar_t value) { buffer_.push_back(internal::CharTraits::convert(value)); return *this; } /** Writes *value* to the stream. */ BasicWriter &operator<<(fmt::BasicStringRef value) { const Char *str = value.c_str(); buffer_.append(str, str + value.size()); return *this; } template BasicWriter &operator<<(const IntFormatSpec &spec) { internal::CharTraits::convert(FillChar()); write_int(spec.value(), spec); return *this; } template BasicWriter &operator<<(const StrFormatSpec &spec) { const StrChar *s = spec.str(); // TODO: error if fill is not convertible to Char write_str(s, std::char_traits::length(s), spec); return *this; } void clear() { buffer_.clear(); } }; template template typename BasicWriter::CharPtr BasicWriter::write_str( const StrChar *s, std::size_t size, const AlignSpec &spec) { CharPtr out = CharPtr(); if (spec.width() > size) { out = grow_buffer(spec.width()); Char fill = static_cast(spec.fill()); if (spec.align() == ALIGN_RIGHT) { std::fill_n(out, spec.width() - size, fill); out += spec.width() - size; } else if (spec.align() == ALIGN_CENTER) { out = fill_padding(out, spec.width(), size, fill); } else { std::fill_n(out + size, spec.width() - size, fill); } } else { out = grow_buffer(size); } std::copy(s, s + size, out); return out; } template template typename fmt::BasicWriter::CharPtr fmt::BasicWriter::prepare_int_buffer( unsigned num_digits, const Spec &spec, const char *prefix, unsigned prefix_size) { unsigned width = spec.width(); Alignment align = spec.align(); Char fill = static_cast(spec.fill()); if (spec.precision() > static_cast(num_digits)) { // Octal prefix '0' is counted as a digit, so ignore it if precision // is specified. if (prefix_size > 0 && prefix[prefix_size - 1] == '0') --prefix_size; unsigned number_size = prefix_size + spec.precision(); AlignSpec subspec(number_size, '0', ALIGN_NUMERIC); if (number_size >= width) return prepare_int_buffer(num_digits, subspec, prefix, prefix_size); buffer_.reserve(width); unsigned fill_size = width - number_size; if (align != ALIGN_LEFT) { CharPtr p = grow_buffer(fill_size); std::fill(p, p + fill_size, fill); } CharPtr result = prepare_int_buffer( num_digits, subspec, prefix, prefix_size); if (align == ALIGN_LEFT) { CharPtr p = grow_buffer(fill_size); std::fill(p, p + fill_size, fill); } return result; } unsigned size = prefix_size + num_digits; if (width <= size) { CharPtr p = grow_buffer(size); std::copy(prefix, prefix + prefix_size, p); return p + size - 1; } CharPtr p = grow_buffer(width); CharPtr end = p + width; if (align == ALIGN_LEFT) { std::copy(prefix, prefix + prefix_size, p); p += size; std::fill(p, end, fill); } else if (align == ALIGN_CENTER) { p = fill_padding(p, width, size, fill); std::copy(prefix, prefix + prefix_size, p); p += size; } else { if (align == ALIGN_NUMERIC) { if (prefix_size != 0) { p = std::copy(prefix, prefix + prefix_size, p); size -= prefix_size; } } else { std::copy(prefix, prefix + prefix_size, end - size); } std::fill(p, end - size, fill); p = end; } return p - 1; } template template void BasicWriter::write_int(T value, const Spec &spec) { unsigned prefix_size = 0; typedef typename internal::IntTraits::MainType UnsignedType; UnsignedType abs_value = value; char prefix[4] = ""; if (internal::is_negative(value)) { prefix[0] = '-'; ++prefix_size; abs_value = 0 - abs_value; } else if (spec.flag(SIGN_FLAG)) { prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; ++prefix_size; } switch (spec.type()) { case 0: case 'd': { unsigned num_digits = internal::count_digits(abs_value); CharPtr p = prepare_int_buffer( num_digits, spec, prefix, prefix_size) + 1 - num_digits; internal::format_decimal(get(p), abs_value, num_digits); break; } case 'x': case 'X': { UnsignedType n = abs_value; if (spec.flag(HASH_FLAG)) { prefix[prefix_size++] = '0'; prefix[prefix_size++] = spec.type(); } unsigned num_digits = 0; do { ++num_digits; } while ((n >>= 4) != 0); Char *p = get(prepare_int_buffer( num_digits, spec, prefix, prefix_size)); n = abs_value; const char *digits = spec.type() == 'x' ? "0123456789abcdef" : "0123456789ABCDEF"; do { *p-- = digits[n & 0xf]; } while ((n >>= 4) != 0); break; } case 'b': case 'B': { UnsignedType n = abs_value; if (spec.flag(HASH_FLAG)) { prefix[prefix_size++] = '0'; prefix[prefix_size++] = spec.type(); } unsigned num_digits = 0; do { ++num_digits; } while ((n >>= 1) != 0); Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); n = abs_value; do { *p-- = '0' + (n & 1); } while ((n >>= 1) != 0); break; } case 'o': { UnsignedType n = abs_value; if (spec.flag(HASH_FLAG)) prefix[prefix_size++] = '0'; unsigned num_digits = 0; do { ++num_digits; } while ((n >>= 3) != 0); Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); n = abs_value; do { *p-- = '0' + (n & 7); } while ((n >>= 3) != 0); break; } default: internal::report_unknown_type( spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); break; } } // Formats a value. template void format(BasicFormatter &f, const Char *format_str, const T &value) { std::basic_ostringstream os; os << value; f.format(format_str, internal::MakeArg(os.str())); } // Reports a system error without throwing an exception. // Can be used to report errors from destructors. void report_system_error(int error_code, StringRef message) FMT_NOEXCEPT(true); #ifdef _WIN32 /** A Windows error. */ class WindowsError : public SystemError { private: void init(int error_code, StringRef format_str, const ArgList &args); public: /** \rst Constructs a :cpp:class:`fmt::WindowsError` object with the description of the form "**: **", where ** is the formatted message and ** is the system message corresponding to the error code. *error_code* is a Windows error code as given by ``GetLastError``. \endrst */ WindowsError(int error_code, StringRef message) { init(error_code, message, ArgList()); } FMT_VARIADIC_CTOR(WindowsError, init, int, StringRef) }; // Reports a Windows error without throwing an exception. // Can be used to report errors from destructors. void report_windows_error(int error_code, StringRef message) FMT_NOEXCEPT(true); #endif enum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; /** Formats a string and prints it to stdout using ANSI escape sequences to specify color (experimental). Example: PrintColored(fmt::RED, "Elapsed time: {0:.2f} seconds") << 1.23; */ void print_colored(Color c, StringRef format, const ArgList &args); /** \rst Formats arguments and returns the result as a string. **Example**:: std::string message = format("The answer is {}", 42); \endrst */ inline std::string format(StringRef format_str, const ArgList &args) { Writer w; w.write(format_str, args); return w.str(); } inline std::wstring format(WStringRef format_str, const ArgList &args) { WWriter w; w.write(format_str, args); return w.str(); } /** \rst Prints formatted data to the file *f*. **Example**:: print(stderr, "Don't {}!", "panic"); \endrst */ void print(std::FILE *f, StringRef format_str, const ArgList &args); /** \rst Prints formatted data to ``stdout``. **Example**:: print("Elapsed time: {0:.2f} seconds", 1.23); \endrst */ inline void print(StringRef format_str, const ArgList &args) { print(stdout, format_str, args); } /** \rst Prints formatted data to the stream *os*. **Example**:: print(cerr, "Don't {}!", "panic"); \endrst */ void print(std::ostream &os, StringRef format_str, const ArgList &args); template void printf(BasicWriter &w, BasicStringRef format, const ArgList &args) { internal::PrintfFormatter().format(w, format, args); } /** \rst Formats arguments and returns the result as a string. **Example**:: std::string message = fmt::sprintf("The answer is %d", 42); \endrst */ inline std::string sprintf(StringRef format, const ArgList &args) { Writer w; printf(w, format, args); return w.str(); } /** \rst Prints formatted data to the file *f*. **Example**:: fmt::fprintf(stderr, "Don't %s!", "panic"); \endrst */ int fprintf(std::FILE *f, StringRef format, const ArgList &args); /** \rst Prints formatted data to ``stdout``. **Example**:: fmt::printf("Elapsed time: %.2f seconds", 1.23); \endrst */ inline int printf(StringRef format, const ArgList &args) { return fprintf(stdout, format, args); } /** Fast integer formatter. */ class FormatInt { private: // Buffer should be large enough to hold all digits (digits10 + 1), // a sign and a null character. enum {BUFFER_SIZE = std::numeric_limits::digits10 + 3}; mutable char buffer_[BUFFER_SIZE]; char *str_; // Formats value in reverse and returns the number of digits. char *format_decimal(ULongLong value) { char *buffer_end = buffer_ + BUFFER_SIZE - 1; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. unsigned index = (value % 100) * 2; value /= 100; *--buffer_end = internal::DIGITS[index + 1]; *--buffer_end = internal::DIGITS[index]; } if (value < 10) { *--buffer_end = static_cast('0' + value); return buffer_end; } unsigned index = static_cast(value * 2); *--buffer_end = internal::DIGITS[index + 1]; *--buffer_end = internal::DIGITS[index]; return buffer_end; } void FormatSigned(LongLong value) { ULongLong abs_value = static_cast(value); bool negative = value < 0; if (negative) abs_value = 0 - abs_value; str_ = format_decimal(abs_value); if (negative) *--str_ = '-'; } public: explicit FormatInt(int value) { FormatSigned(value); } explicit FormatInt(long value) { FormatSigned(value); } explicit FormatInt(LongLong value) { FormatSigned(value); } explicit FormatInt(unsigned value) : str_(format_decimal(value)) {} explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {} explicit FormatInt(ULongLong value) : str_(format_decimal(value)) {} /** Returns the number of characters written to the output buffer. */ std::size_t size() const { return buffer_ - str_ + BUFFER_SIZE - 1; } /** Returns a pointer to the output buffer content. No terminating null character is appended. */ const char *data() const { return str_; } /** Returns a pointer to the output buffer content with terminating null character appended. */ const char *c_str() const { buffer_[BUFFER_SIZE - 1] = '\0'; return str_; } /** Returns the content of the output buffer as an `std::string`. */ std::string str() const { return std::string(str_, size()); } }; // Formats a decimal integer value writing into buffer and returns // a pointer to the end of the formatted string. This function doesn't // write a terminating null character. template inline void format_decimal(char *&buffer, T value) { typename internal::IntTraits::MainType abs_value = value; if (internal::is_negative(value)) { *buffer++ = '-'; abs_value = 0 - abs_value; } if (abs_value < 100) { if (abs_value < 10) { *buffer++ = static_cast('0' + abs_value); return; } unsigned index = static_cast(abs_value * 2); *buffer++ = internal::DIGITS[index]; *buffer++ = internal::DIGITS[index + 1]; return; } unsigned num_digits = internal::count_digits(abs_value); internal::format_decimal(buffer, abs_value, num_digits); buffer += num_digits; } } #if FMT_GCC_VERSION // Use the system_header pragma to suppress warnings about variadic macros // because suppressing -Wvariadic-macros with the diagnostic pragma doesn't // work. It is used at the end because we want to suppress as little warnings // as possible. # pragma GCC system_header #endif // This is used to work around VC++ bugs in handling variadic macros. #define FMT_EXPAND(args) args // Returns the number of arguments. // Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s. #define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N()) #define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__)) #define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N #define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 #define FMT_CONCAT(a, b) a##b #define FMT_FOR_EACH_(N, f, ...) \ FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__)) #define FMT_FOR_EACH(f, ...) \ FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__)) #define FMT_ADD_ARG_NAME(type, index) type arg##index #define FMT_GET_ARG_NAME(type, index) arg##index #if FMT_USE_VARIADIC_TEMPLATES # define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ template \ ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ const Args & ... args) { \ using fmt::internal::Arg; \ const Arg array[fmt::internal::NonZero::VALUE] = { \ fmt::internal::MakeArg(args)... \ }; \ call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \ fmt::ArgList(array, sizeof...(Args))); \ } #else // Defines a wrapper for a function taking __VA_ARGS__ arguments // and n additional arguments of arbitrary types. # define FMT_WRAP(Char, ReturnType, func, call, n, ...) \ template \ inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ FMT_GEN(n, FMT_MAKE_ARG)) { \ const fmt::internal::Arg args[] = {FMT_GEN(n, FMT_MAKE_REF_##Char)}; \ call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \ fmt::ArgList(args, sizeof(args) / sizeof(*args))); \ } # define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) { \ call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \ } \ FMT_WRAP(Char, ReturnType, func, call, 1, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 2, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 3, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 4, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 5, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 6, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 7, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 8, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 9, __VA_ARGS__) \ FMT_WRAP(Char, ReturnType, func, call, 10, __VA_ARGS__) #endif // FMT_USE_VARIADIC_TEMPLATES /** \rst Defines a variadic function with the specified return type, function name and argument types passed as variable arguments to this macro. **Example**:: void print_error(const char *file, int line, const char *format, const fmt::ArgList &args) { fmt::print("{}: {}: ", file, line); fmt::print(format, args); } FMT_VARIADIC(void, print_error, const char *, int, const char *) ``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that don't implement variadic templates. You don't have to use this macro if you don't need legacy compiler support and can use variadic templates directly:: template void print_error(const char *file, int line, const char *format, const Args & ... args) { fmt::print("{}: {}: ", file, line); fmt::print(format, args...); } \endrst */ #define FMT_VARIADIC(ReturnType, func, ...) \ FMT_VARIADIC_(char, ReturnType, func, return func, __VA_ARGS__) #define FMT_VARIADIC_W(ReturnType, func, ...) \ FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__) namespace fmt { FMT_VARIADIC(std::string, format, StringRef) FMT_VARIADIC_W(std::wstring, format, WStringRef) FMT_VARIADIC(void, print, StringRef) FMT_VARIADIC(void, print, std::FILE *, StringRef) FMT_VARIADIC(void, print, std::ostream &, StringRef) FMT_VARIADIC(void, print_colored, Color, StringRef) FMT_VARIADIC(std::string, sprintf, StringRef) FMT_VARIADIC(int, printf, StringRef) FMT_VARIADIC(int, fprintf, std::FILE *, StringRef) } // Restore warnings. #if FMT_GCC_VERSION >= 406 # pragma GCC diagnostic pop #endif #endif // FMT_FORMAT_H_ Index: ps/trunk/source/third_party/mikktspace/mikktspace.cpp =================================================================== --- ps/trunk/source/third_party/mikktspace/mikktspace.cpp (revision 23415) +++ ps/trunk/source/third_party/mikktspace/mikktspace.cpp (revision 23416) @@ -1,1913 +1,1911 @@ // Slightly modified version of mikktspace, by Wildfire Games, for 0 A.D. // // Motivation for changes: // * For quietness with our default warning flags, some warnings are // explicitly disabled. #include "precompiled.h" #ifdef _MSC_VER -#if _MSC_VER > 1800 # pragma warning(disable:4456) // hides previous local declaration -#endif # pragma warning(disable:4189) // local variable is initialized but not referenced #endif #ifdef __GNUC__ # pragma GCC diagnostic ignored "-Wunused-variable" #endif /** \file mikktspace/mikktspace.c * \ingroup mikktspace */ /** * Copyright (C) 2011 by Morten S. Mikkelsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ #include #include #include #include #include #include #include "mikktspace.h" #define TFALSE 0 #define TTRUE 1 #ifndef M_PI #define M_PI 3.1415926535897932384626433832795 #endif #define INTERNAL_RND_SORT_SEED 39871946 // internal structure typedef struct { float x, y, z; } SVec3; static tbool veq( const SVec3 v1, const SVec3 v2 ) { return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z); } static SVec3 vadd( const SVec3 v1, const SVec3 v2 ) { SVec3 vRes; vRes.x = v1.x + v2.x; vRes.y = v1.y + v2.y; vRes.z = v1.z + v2.z; return vRes; } static SVec3 vsub( const SVec3 v1, const SVec3 v2 ) { SVec3 vRes; vRes.x = v1.x - v2.x; vRes.y = v1.y - v2.y; vRes.z = v1.z - v2.z; return vRes; } static SVec3 vscale(const float fS, const SVec3 v) { SVec3 vRes; vRes.x = fS * v.x; vRes.y = fS * v.y; vRes.z = fS * v.z; return vRes; } static float LengthSquared( const SVec3 v ) { return v.x*v.x + v.y*v.y + v.z*v.z; } static float Length( const SVec3 v ) { return sqrtf(LengthSquared(v)); } static SVec3 Normalize( const SVec3 v ) { return vscale(1 / Length(v), v); } static float vdot( const SVec3 v1, const SVec3 v2) { return v1.x*v2.x + v1.y*v2.y + v1.z*v2.z; } static tbool NotZero(const float fX) { // could possibly use FLT_EPSILON instead return fabsf(fX) > FLT_MIN; } static tbool VNotZero(const SVec3 v) { // might change this to an epsilon based test return NotZero(v.x) || NotZero(v.y) || NotZero(v.z); } typedef struct { int iNrFaces; int * pTriMembers; } SSubGroup; typedef struct { int iNrFaces; int * pFaceIndices; int iVertexRepresentitive; tbool bOrientPreservering; } SGroup; // #define MARK_DEGENERATE 1 #define QUAD_ONE_DEGEN_TRI 2 #define GROUP_WITH_ANY 4 #define ORIENT_PRESERVING 8 typedef struct { int FaceNeighbors[3]; SGroup * AssignedGroup[3]; // normalized first order face derivatives SVec3 vOs, vOt; float fMagS, fMagT; // original magnitudes // determines if the current and the next triangle are a quad. int iOrgFaceNumber; int iFlag, iTSpacesOffs; unsigned char vert_num[4]; } STriInfo; typedef struct { SVec3 vOs; float fMagS; SVec3 vOt; float fMagT; int iCounter; // this is to average back into quads. tbool bOrient; } STSpace; static int GenerateInitialVerticesIndexList(STriInfo pTriInfos[], int piTriList_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); static void GenerateSharedVerticesIndexList(int piTriList_in_and_out[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn); static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn); static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, const SMikkTSpaceContext * pContext); static int MakeIndex(const int iFace, const int iVert) { assert(iVert>=0 && iVert<4 && iFace>=0); return (iFace<<2) | (iVert&0x3); } static void IndexToData(int * piFace, int * piVert, const int iIndexIn) { piVert[0] = iIndexIn&0x3; piFace[0] = iIndexIn>>2; } static STSpace AvgTSpace(const STSpace * pTS0, const STSpace * pTS1) { STSpace ts_res; // this if is important. Due to floating point precision // averaging when ts0==ts1 will cause a slight difference // which results in tangent space splits later on if (pTS0->fMagS==pTS1->fMagS && pTS0->fMagT==pTS1->fMagT && veq(pTS0->vOs,pTS1->vOs) && veq(pTS0->vOt, pTS1->vOt)) { ts_res.fMagS = pTS0->fMagS; ts_res.fMagT = pTS0->fMagT; ts_res.vOs = pTS0->vOs; ts_res.vOt = pTS0->vOt; } else { ts_res.fMagS = 0.5f*(pTS0->fMagS+pTS1->fMagS); ts_res.fMagT = 0.5f*(pTS0->fMagT+pTS1->fMagT); ts_res.vOs = vadd(pTS0->vOs,pTS1->vOs); ts_res.vOt = vadd(pTS0->vOt,pTS1->vOt); if ( VNotZero(ts_res.vOs) ) ts_res.vOs = Normalize(ts_res.vOs); if ( VNotZero(ts_res.vOt) ) ts_res.vOt = Normalize(ts_res.vOt); } return ts_res; } static SVec3 GetPosition(const SMikkTSpaceContext * pContext, const int index); static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index); static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index); // degen triangles static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris); static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris); tbool genTangSpaceDefault(const SMikkTSpaceContext * pContext) { return genTangSpace(pContext, 180.0f); } tbool genTangSpace(const SMikkTSpaceContext * pContext, const float fAngularThreshold) { // count nr_triangles int * piTriListIn = NULL, * piGroupTrianglesBuffer = NULL; STriInfo * pTriInfos = NULL; SGroup * pGroups = NULL; STSpace * psTspace = NULL; int iNrTrianglesIn = 0, f=0, t=0, i=0; int iNrTSPaces = 0, iTotTris = 0, iDegenTriangles = 0, iNrMaxGroups = 0; int iNrActiveGroups = 0, index = 0; const int iNrFaces = pContext->m_pInterface->m_getNumFaces(pContext); tbool bRes = TFALSE; const float fThresCos = (float) cos((fAngularThreshold*(float)M_PI)/180.0f); // verify all call-backs have been set if ( pContext->m_pInterface->m_getNumFaces==NULL || pContext->m_pInterface->m_getNumVerticesOfFace==NULL || pContext->m_pInterface->m_getPosition==NULL || pContext->m_pInterface->m_getNormal==NULL || pContext->m_pInterface->m_getTexCoord==NULL ) return TFALSE; // count triangles on supported faces for (f=0; fm_pInterface->m_getNumVerticesOfFace(pContext, f); if (verts==3) ++iNrTrianglesIn; else if(verts==4) iNrTrianglesIn += 2; } if (iNrTrianglesIn<=0) return TFALSE; // allocate memory for an index list piTriListIn = (int *) malloc(sizeof(int)*3*iNrTrianglesIn); pTriInfos = (STriInfo *) malloc(sizeof(STriInfo)*iNrTrianglesIn); if (piTriListIn==NULL || pTriInfos==NULL) { if (piTriListIn!=NULL) free(piTriListIn); if (pTriInfos!=NULL) free(pTriInfos); return TFALSE; } // make an initial triangle --> face index list iNrTSPaces = GenerateInitialVerticesIndexList(pTriInfos, piTriListIn, pContext, iNrTrianglesIn); // make a welded index list of identical positions and attributes (pos, norm, texc) //printf("gen welded index list begin\n"); GenerateSharedVerticesIndexList(piTriListIn, pContext, iNrTrianglesIn); //printf("gen welded index list end\n"); // Mark all degenerate triangles iTotTris = iNrTrianglesIn; iDegenTriangles = 0; for (t=0; tm_pInterface->m_getNumVerticesOfFace(pContext, f); if (verts!=3 && verts!=4) continue; // I've decided to let degenerate triangles and group-with-anythings // vary between left/right hand coordinate systems at the vertices. // All healthy triangles on the other hand are built to always be either or. /*// force the coordinate system orientation to be uniform for every face. // (this is already the case for good triangles but not for // degenerate ones and those with bGroupWithAnything==true) bool bOrient = psTspace[index].bOrient; if (psTspace[index].iCounter == 0) // tspace was not derived from a group { // look for a space created in GenerateTSpaces() by iCounter>0 bool bNotFound = true; int i=1; while (i 0) bNotFound=false; else ++i; } if (!bNotFound) bOrient = psTspace[index+i].bOrient; }*/ // set data for (i=0; ivOs.x, pTSpace->vOs.y, pTSpace->vOs.z}; float bitang[] = {pTSpace->vOt.x, pTSpace->vOt.y, pTSpace->vOt.z}; if (pContext->m_pInterface->m_setTSpace!=NULL) pContext->m_pInterface->m_setTSpace(pContext, tang, bitang, pTSpace->fMagS, pTSpace->fMagT, pTSpace->bOrient, f, i); if (pContext->m_pInterface->m_setTSpaceBasic!=NULL) pContext->m_pInterface->m_setTSpaceBasic(pContext, tang, pTSpace->bOrient==TTRUE ? 1.0f : (-1.0f), f, i); ++index; } } free(psTspace); return TTRUE; } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// typedef struct { float vert[3]; int index; } STmpVert; const int g_iCells = 2048; #ifdef _MSC_VER #define NOINLINE __declspec(noinline) #else #define NOINLINE __attribute__ ((noinline)) #endif // it is IMPORTANT that this function is called to evaluate the hash since // inlining could potentially reorder instructions and generate different // results for the same effective input value fVal. NOINLINE int FindGridCell(const float fMin, const float fMax, const float fVal) { const float fIndex = g_iCells * ((fVal-fMin)/(fMax-fMin)); const int iIndex = fIndex<0?0:((int)fIndex); return iIndex vP.x) vMin.x = vP.x; else if(vMax.x < vP.x) vMax.x = vP.x; if (vMin.y > vP.y) vMin.y = vP.y; else if(vMax.y < vP.y) vMax.y = vP.y; if (vMin.z > vP.z) vMin.z = vP.z; else if(vMax.z < vP.z) vMax.z = vP.z; } vDim = vsub(vMax,vMin); iChannel = 0; fMin = vMin.x; fMax=vMax.x; if (vDim.y>vDim.x && vDim.y>vDim.z) { iChannel=1; fMin = vMin.y, fMax=vMax.y; } else if(vDim.z>vDim.x) { iChannel=2; fMin = vMin.z, fMax=vMax.z; } // make allocations piHashTable = (int *) malloc(sizeof(int)*iNrTrianglesIn*3); piHashCount = (int *) malloc(sizeof(int)*g_iCells); piHashOffsets = (int *) malloc(sizeof(int)*g_iCells); piHashCount2 = (int *) malloc(sizeof(int)*g_iCells); if (piHashTable==NULL || piHashCount==NULL || piHashOffsets==NULL || piHashCount2==NULL) { if (piHashTable!=NULL) free(piHashTable); if (piHashCount!=NULL) free(piHashCount); if (piHashOffsets!=NULL) free(piHashOffsets); if (piHashCount2!=NULL) free(piHashCount2); GenerateSharedVerticesIndexListSlow(piTriList_in_and_out, pContext, iNrTrianglesIn); return; } memset(piHashCount, 0, sizeof(int)*g_iCells); memset(piHashCount2, 0, sizeof(int)*g_iCells); // count amount of elements in each cell unit for (i=0; i<(iNrTrianglesIn*3); i++) { const int index = piTriList_in_and_out[i]; const SVec3 vP = GetPosition(pContext, index); const float fVal = iChannel==0 ? vP.x : (iChannel==1 ? vP.y : vP.z); const int iCell = FindGridCell(fMin, fMax, fVal); ++piHashCount[iCell]; } // evaluate start index of each cell. piHashOffsets[0]=0; for (k=1; kpTmpVert[l].vert[c]) fvMin[c]=pTmpVert[l].vert[c]; else if(fvMax[c]dx && dy>dz) channel=1; else if(dz>dx) channel=2; fSep = 0.5f*(fvMax[channel]+fvMin[channel]); // terminate recursion when the separation/average value // is no longer strictly between fMin and fMax values. if (fSep>=fvMax[channel] || fSep<=fvMin[channel]) { // complete the weld for (l=iL_in; l<=iR_in; l++) { int i = pTmpVert[l].index; const int index = piTriList_in_and_out[i]; const SVec3 vP = GetPosition(pContext, index); const SVec3 vN = GetNormal(pContext, index); const SVec3 vT = GetTexCoord(pContext, index); tbool bNotFound = TTRUE; int l2=iL_in, i2rec=-1; while (l20); // at least 2 entries // separate (by fSep) all points between iL_in and iR_in in pTmpVert[] while (iL < iR) { tbool bReadyLeftSwap = TFALSE, bReadyRightSwap = TFALSE; while ((!bReadyLeftSwap) && iL=iL_in && iL<=iR_in); bReadyLeftSwap = !(pTmpVert[iL].vert[channel]=iL_in && iR<=iR_in); bReadyRightSwap = pTmpVert[iR].vert[channel]m_pInterface->m_getNumFaces(pContext); f++) { const int verts = pContext->m_pInterface->m_getNumVerticesOfFace(pContext, f); if (verts!=3 && verts!=4) continue; pTriInfos[iDstTriIndex].iOrgFaceNumber = f; pTriInfos[iDstTriIndex].iTSpacesOffs = iTSpacesOffs; if (verts==3) { unsigned char * pVerts = pTriInfos[iDstTriIndex].vert_num; pVerts[0]=0; pVerts[1]=1; pVerts[2]=2; piTriList_out[iDstTriIndex*3+0] = MakeIndex(f, 0); piTriList_out[iDstTriIndex*3+1] = MakeIndex(f, 1); piTriList_out[iDstTriIndex*3+2] = MakeIndex(f, 2); ++iDstTriIndex; // next } else { { pTriInfos[iDstTriIndex+1].iOrgFaceNumber = f; pTriInfos[iDstTriIndex+1].iTSpacesOffs = iTSpacesOffs; } { // need an order independent way to evaluate // tspace on quads. This is done by splitting // along the shortest diagonal. const int i0 = MakeIndex(f, 0); const int i1 = MakeIndex(f, 1); const int i2 = MakeIndex(f, 2); const int i3 = MakeIndex(f, 3); const SVec3 T0 = GetTexCoord(pContext, i0); const SVec3 T1 = GetTexCoord(pContext, i1); const SVec3 T2 = GetTexCoord(pContext, i2); const SVec3 T3 = GetTexCoord(pContext, i3); const float distSQ_02 = LengthSquared(vsub(T2,T0)); const float distSQ_13 = LengthSquared(vsub(T3,T1)); tbool bQuadDiagIs_02; if (distSQ_02m_pInterface->m_getPosition(pContext, pos, iF, iI); res.x=pos[0]; res.y=pos[1]; res.z=pos[2]; return res; } static SVec3 GetNormal(const SMikkTSpaceContext * pContext, const int index) { int iF, iI; SVec3 res; float norm[3]; IndexToData(&iF, &iI, index); pContext->m_pInterface->m_getNormal(pContext, norm, iF, iI); res.x=norm[0]; res.y=norm[1]; res.z=norm[2]; return res; } static SVec3 GetTexCoord(const SMikkTSpaceContext * pContext, const int index) { int iF, iI; SVec3 res; float texc[2]; IndexToData(&iF, &iI, index); pContext->m_pInterface->m_getTexCoord(pContext, texc, iF, iI); res.x=texc[0]; res.y=texc[1]; res.z=1.0f; return res; } ///////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// typedef union { struct { int i0, i1, f; }; int array[3]; } SEdge; static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn); static void BuildNeighborsSlow(STriInfo pTriInfos[], const int piTriListIn[], const int iNrTrianglesIn); // returns the texture area times 2 static float CalcTexArea(const SMikkTSpaceContext * pContext, const int indices[]) { const SVec3 t1 = GetTexCoord(pContext, indices[0]); const SVec3 t2 = GetTexCoord(pContext, indices[1]); const SVec3 t3 = GetTexCoord(pContext, indices[2]); const float t21x = t2.x-t1.x; const float t21y = t2.y-t1.y; const float t31x = t3.x-t1.x; const float t31y = t3.y-t1.y; const float fSignedAreaSTx2 = t21x*t31y - t21y*t31x; return fSignedAreaSTx2<0 ? (-fSignedAreaSTx2) : fSignedAreaSTx2; } static void InitTriInfo(STriInfo pTriInfos[], const int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn) { int f=0, i=0, t=0; // pTriInfos[f].iFlag is cleared in GenerateInitialVerticesIndexList() which is called before this function. // generate neighbor info list for (f=0; f0 ? ORIENT_PRESERVING : 0); if ( NotZero(fSignedAreaSTx2) ) { const float fAbsArea = fabsf(fSignedAreaSTx2); const float fLenOs = Length(vOs); const float fLenOt = Length(vOt); const float fS = (pTriInfos[f].iFlag&ORIENT_PRESERVING)==0 ? (-1.0f) : 1.0f; if ( NotZero(fLenOs) ) pTriInfos[f].vOs = vscale(fS/fLenOs, vOs); if ( NotZero(fLenOt) ) pTriInfos[f].vOt = vscale(fS/fLenOt, vOt); // evaluate magnitudes prior to normalization of vOs and vOt pTriInfos[f].fMagS = fLenOs / fAbsArea; pTriInfos[f].fMagT = fLenOt / fAbsArea; // if this is a good triangle if ( NotZero(pTriInfos[f].fMagS) && NotZero(pTriInfos[f].fMagT)) pTriInfos[f].iFlag &= (~GROUP_WITH_ANY); } } // force otherwise healthy quads to a fixed orientation while (t<(iNrTrianglesIn-1)) { const int iFO_a = pTriInfos[t].iOrgFaceNumber; const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; if (iFO_a==iFO_b) // this is a quad { const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; // bad triangles should already have been removed by // DegenPrologue(), but just in case check bIsDeg_a and bIsDeg_a are false if ((bIsDeg_a||bIsDeg_b)==TFALSE) { const tbool bOrientA = (pTriInfos[t].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; const tbool bOrientB = (pTriInfos[t+1].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; // if this happens the quad has extremely bad mapping!! if (bOrientA!=bOrientB) { //printf("found quad with bad mapping\n"); tbool bChooseOrientFirstTri = TFALSE; if ((pTriInfos[t+1].iFlag&GROUP_WITH_ANY)!=0) bChooseOrientFirstTri = TTRUE; else if( CalcTexArea(pContext, &piTriListIn[t*3+0]) >= CalcTexArea(pContext, &piTriListIn[(t+1)*3+0]) ) bChooseOrientFirstTri = TTRUE; // force match { const int t0 = bChooseOrientFirstTri ? t : (t+1); const int t1 = bChooseOrientFirstTri ? (t+1) : t; pTriInfos[t1].iFlag &= (~ORIENT_PRESERVING); // clear first pTriInfos[t1].iFlag |= (pTriInfos[t0].iFlag&ORIENT_PRESERVING); // copy bit } } } t += 2; } else ++t; } // match up edge pairs { SEdge * pEdges = (SEdge *) malloc(sizeof(SEdge)*iNrTrianglesIn*3); if (pEdges==NULL) BuildNeighborsSlow(pTriInfos, piTriListIn, iNrTrianglesIn); else { BuildNeighborsFast(pTriInfos, pEdges, piTriListIn, iNrTrianglesIn); free(pEdges); } } } ///////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup); static void AddTriToGroup(SGroup * pGroup, const int iTriIndex); static int Build4RuleGroups(STriInfo pTriInfos[], SGroup pGroups[], int piGroupTrianglesBuffer[], const int piTriListIn[], const int iNrTrianglesIn) { const int iNrMaxGroups = iNrTrianglesIn*3; int iNrActiveGroups = 0; int iOffset = 0, f=0, i=0; for (f=0; fiVertexRepresentitive = vert_index; pTriInfos[f].AssignedGroup[i]->bOrientPreservering = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0; pTriInfos[f].AssignedGroup[i]->iNrFaces = 0; pTriInfos[f].AssignedGroup[i]->pFaceIndices = &piGroupTrianglesBuffer[iOffset]; ++iNrActiveGroups; AddTriToGroup(pTriInfos[f].AssignedGroup[i], f); bOrPre = (pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; neigh_indexL = pTriInfos[f].FaceNeighbors[i]; neigh_indexR = pTriInfos[f].FaceNeighbors[i>0?(i-1):2]; if (neigh_indexL>=0) // neighbor { const tbool bAnswer = AssignRecur(piTriListIn, pTriInfos, neigh_indexL, pTriInfos[f].AssignedGroup[i] ); const tbool bOrPre2 = (pTriInfos[neigh_indexL].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; assert(bAnswer || bDiff); } if (neigh_indexR>=0) // neighbor { const tbool bAnswer = AssignRecur(piTriListIn, pTriInfos, neigh_indexR, pTriInfos[f].AssignedGroup[i] ); const tbool bOrPre2 = (pTriInfos[neigh_indexR].iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; const tbool bDiff = bOrPre!=bOrPre2 ? TTRUE : TFALSE; assert(bAnswer || bDiff); } // update offset iOffset += pTriInfos[f].AssignedGroup[i]->iNrFaces; // since the groups are disjoint a triangle can never // belong to more than 3 groups. Subsequently something // is completely screwed if this assertion ever hits. assert(iOffset <= iNrMaxGroups); } } } return iNrActiveGroups; } static void AddTriToGroup(SGroup * pGroup, const int iTriIndex) { pGroup->pFaceIndices[pGroup->iNrFaces] = iTriIndex; ++pGroup->iNrFaces; } static tbool AssignRecur(const int piTriListIn[], STriInfo psTriInfos[], const int iMyTriIndex, SGroup * pGroup) { STriInfo * pMyTriInfo = &psTriInfos[iMyTriIndex]; // track down vertex const int iVertRep = pGroup->iVertexRepresentitive; const int * pVerts = &piTriListIn[3*iMyTriIndex+0]; int i=-1; if (pVerts[0]==iVertRep) i=0; else if(pVerts[1]==iVertRep) i=1; else if(pVerts[2]==iVertRep) i=2; assert(i>=0 && i<3); // early out if (pMyTriInfo->AssignedGroup[i] == pGroup) return TTRUE; else if(pMyTriInfo->AssignedGroup[i]!=NULL) return TFALSE; if ((pMyTriInfo->iFlag&GROUP_WITH_ANY)!=0) { // first to group with a group-with-anything triangle // determines it's orientation. // This is the only existing order dependency in the code!! if ( pMyTriInfo->AssignedGroup[0] == NULL && pMyTriInfo->AssignedGroup[1] == NULL && pMyTriInfo->AssignedGroup[2] == NULL ) { pMyTriInfo->iFlag &= (~ORIENT_PRESERVING); pMyTriInfo->iFlag |= (pGroup->bOrientPreservering ? ORIENT_PRESERVING : 0); } } { const tbool bOrient = (pMyTriInfo->iFlag&ORIENT_PRESERVING)!=0 ? TTRUE : TFALSE; if (bOrient != pGroup->bOrientPreservering) return TFALSE; } AddTriToGroup(pGroup, iMyTriIndex); pMyTriInfo->AssignedGroup[i] = pGroup; { const int neigh_indexL = pMyTriInfo->FaceNeighbors[i]; const int neigh_indexR = pMyTriInfo->FaceNeighbors[i>0?(i-1):2]; if (neigh_indexL>=0) AssignRecur(piTriListIn, psTriInfos, neigh_indexL, pGroup); if (neigh_indexR>=0) AssignRecur(piTriListIn, psTriInfos, neigh_indexR, pGroup); } return TTRUE; } ///////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////// static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2); static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed); static STSpace EvalTspace(int face_indices[], const int iFaces, const int piTriListIn[], const STriInfo pTriInfos[], const SMikkTSpaceContext * pContext, const int iVertexRepresentitive); static tbool GenerateTSpaces(STSpace psTspace[], const STriInfo pTriInfos[], const SGroup pGroups[], const int iNrActiveGroups, const int piTriListIn[], const float fThresCos, const SMikkTSpaceContext * pContext) { STSpace * pSubGroupTspace = NULL; SSubGroup * pUniSubGroups = NULL; int * pTmpMembers = NULL; int iMaxNrFaces=0, iUniqueTspaces=0, g=0, i=0; for (g=0; giNrFaces; i++) // triangles { const int f = pGroup->pFaceIndices[i]; // triangle number int index=-1, iVertIndex=-1, iOF_1=-1, iMembers=0, j=0, l=0; SSubGroup tmp_group; tbool bFound; SVec3 n, vOs, vOt; if (pTriInfos[f].AssignedGroup[0]==pGroup) index=0; else if(pTriInfos[f].AssignedGroup[1]==pGroup) index=1; else if(pTriInfos[f].AssignedGroup[2]==pGroup) index=2; assert(index>=0 && index<3); iVertIndex = piTriListIn[f*3+index]; assert(iVertIndex==pGroup->iVertexRepresentitive); // is normalized already n = GetNormal(pContext, iVertIndex); // project vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); if ( VNotZero(vOs) ) vOs = Normalize(vOs); if ( VNotZero(vOt) ) vOt = Normalize(vOt); // original face number iOF_1 = pTriInfos[f].iOrgFaceNumber; iMembers = 0; for (j=0; jiNrFaces; j++) { const int t = pGroup->pFaceIndices[j]; // triangle number const int iOF_2 = pTriInfos[t].iOrgFaceNumber; // project SVec3 vOs2 = vsub(pTriInfos[t].vOs, vscale(vdot(n,pTriInfos[t].vOs), n)); SVec3 vOt2 = vsub(pTriInfos[t].vOt, vscale(vdot(n,pTriInfos[t].vOt), n)); if ( VNotZero(vOs2) ) vOs2 = Normalize(vOs2); if ( VNotZero(vOt2) ) vOt2 = Normalize(vOt2); { const tbool bAny = ( (pTriInfos[f].iFlag | pTriInfos[t].iFlag) & GROUP_WITH_ANY )!=0 ? TTRUE : TFALSE; // make sure triangles which belong to the same quad are joined. const tbool bSameOrgFace = iOF_1==iOF_2 ? TTRUE : TFALSE; const float fCosS = vdot(vOs,vOs2); const float fCosT = vdot(vOt,vOt2); assert(f!=t || bSameOrgFace); // sanity check if (bAny || bSameOrgFace || (fCosS>fThresCos && fCosT>fThresCos)) pTmpMembers[iMembers++] = t; } } // sort pTmpMembers tmp_group.iNrFaces = iMembers; tmp_group.pTriMembers = pTmpMembers; if (iMembers>1) { unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? QuickSort(pTmpMembers, 0, iMembers-1, uSeed); } // look for an existing match bFound = TFALSE; l=0; while (liVertexRepresentitive); ++iUniqueSubGroups; } // output tspace { const int iOffs = pTriInfos[f].iTSpacesOffs; const int iVert = pTriInfos[f].vert_num[index]; STSpace * pTS_out = &psTspace[iOffs+iVert]; assert(pTS_out->iCounter<2); assert(((pTriInfos[f].iFlag&ORIENT_PRESERVING)!=0) == pGroup->bOrientPreservering); if (pTS_out->iCounter==1) { *pTS_out = AvgTSpace(pTS_out, &pSubGroupTspace[l]); pTS_out->iCounter = 2; // update counter pTS_out->bOrient = pGroup->bOrientPreservering; } else { assert(pTS_out->iCounter==0); *pTS_out = pSubGroupTspace[l]; pTS_out->iCounter = 1; // update counter pTS_out->bOrient = pGroup->bOrientPreservering; } } } // clean up and offset iUniqueTspaces for (s=0; s=0 && i<3); // project index = piTriListIn[3*f+i]; n = GetNormal(pContext, index); vOs = vsub(pTriInfos[f].vOs, vscale(vdot(n,pTriInfos[f].vOs), n)); vOt = vsub(pTriInfos[f].vOt, vscale(vdot(n,pTriInfos[f].vOt), n)); if ( VNotZero(vOs) ) vOs = Normalize(vOs); if ( VNotZero(vOt) ) vOt = Normalize(vOt); i2 = piTriListIn[3*f + (i<2?(i+1):0)]; i1 = piTriListIn[3*f + i]; i0 = piTriListIn[3*f + (i>0?(i-1):2)]; p0 = GetPosition(pContext, i0); p1 = GetPosition(pContext, i1); p2 = GetPosition(pContext, i2); v1 = vsub(p0,p1); v2 = vsub(p2,p1); // project v1 = vsub(v1, vscale(vdot(n,v1),n)); if( VNotZero(v1) ) v1 = Normalize(v1); v2 = vsub(v2, vscale(vdot(n,v2),n)); if( VNotZero(v2) ) v2 = Normalize(v2); // weight contribution by the angle // between the two edge vectors fCos = vdot(v1,v2); fCos=fCos>1?1:(fCos<(-1) ? (-1) : fCos); fAngle = (float) acos(fCos); fMagS = pTriInfos[f].fMagS; fMagT = pTriInfos[f].fMagT; res.vOs=vadd(res.vOs, vscale(fAngle,vOs)); res.vOt=vadd(res.vOt,vscale(fAngle,vOt)); res.fMagS+=(fAngle*fMagS); res.fMagT+=(fAngle*fMagT); fAngleSum += fAngle; } } // normalize if ( VNotZero(res.vOs) ) res.vOs = Normalize(res.vOs); if ( VNotZero(res.vOt) ) res.vOt = Normalize(res.vOt); if (fAngleSum>0) { res.fMagS /= fAngleSum; res.fMagT /= fAngleSum; } return res; } static tbool CompareSubGroups(const SSubGroup * pg1, const SSubGroup * pg2) { tbool bStillSame=TTRUE; int i=0; if (pg1->iNrFaces!=pg2->iNrFaces) return TFALSE; while (iiNrFaces && bStillSame) { bStillSame = pg1->pTriMembers[i]==pg2->pTriMembers[i] ? TTRUE : TFALSE; if (bStillSame) ++i; } return bStillSame; } static void QuickSort(int* pSortBuffer, int iLeft, int iRight, unsigned int uSeed) { int iL, iR, n, index, iMid, iTmp; // Random unsigned int t=uSeed&31; t=(uSeed<>(32-t)); uSeed=uSeed+t+3; // Random end iL=iLeft; iR=iRight; n = (iR-iL)+1; assert(n>=0); index = (int) (uSeed%n); iMid=pSortBuffer[index + iL]; do { while (pSortBuffer[iL] < iMid) ++iL; while (pSortBuffer[iR] > iMid) --iR; if (iL <= iR) { iTmp = pSortBuffer[iL]; pSortBuffer[iL] = pSortBuffer[iR]; pSortBuffer[iR] = iTmp; ++iL; --iR; } } while (iL <= iR); if (iLeft < iR) QuickSort(pSortBuffer, iLeft, iR, uSeed); if (iL < iRight) QuickSort(pSortBuffer, iL, iRight, uSeed); } ///////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// static void QuickSortEdges(SEdge * pSortBuffer, int iLeft, int iRight, const int channel, unsigned int uSeed); static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in); static void BuildNeighborsFast(STriInfo pTriInfos[], SEdge * pEdges, const int piTriListIn[], const int iNrTrianglesIn) { // build array of edges unsigned int uSeed = INTERNAL_RND_SORT_SEED; // could replace with a random seed? int iEntries=0, iCurStartIndex=-1, f=0, i=0; for (f=0; f pSortBuffer[iRight].array[channel]) { sTmp = pSortBuffer[iLeft]; pSortBuffer[iLeft] = pSortBuffer[iRight]; pSortBuffer[iRight] = sTmp; } return; } // Random t=uSeed&31; t=(uSeed<>(32-t)); uSeed=uSeed+t+3; // Random end iL=iLeft, iR=iRight; n = (iR-iL)+1; assert(n>=0); index = (int) (uSeed%n); iMid=pSortBuffer[index + iL].array[channel]; do { while (pSortBuffer[iL].array[channel] < iMid) ++iL; while (pSortBuffer[iR].array[channel] > iMid) --iR; if (iL <= iR) { sTmp = pSortBuffer[iL]; pSortBuffer[iL] = pSortBuffer[iR]; pSortBuffer[iR] = sTmp; ++iL; --iR; } } while (iL <= iR); if (iLeft < iR) QuickSortEdges(pSortBuffer, iLeft, iR, channel, uSeed); if (iL < iRight) QuickSortEdges(pSortBuffer, iL, iRight, channel, uSeed); } // resolve ordering and edge number static void GetEdge(int * i0_out, int * i1_out, int * edgenum_out, const int indices[], const int i0_in, const int i1_in) { *edgenum_out = -1; // test if first index is on the edge if (indices[0]==i0_in || indices[0]==i1_in) { // test if second index is on the edge if (indices[1]==i0_in || indices[1]==i1_in) { edgenum_out[0]=0; // first edge i0_out[0]=indices[0]; i1_out[0]=indices[1]; } else { edgenum_out[0]=2; // third edge i0_out[0]=indices[2]; i1_out[0]=indices[0]; } } else { // only second and third index is on the edge edgenum_out[0]=1; // second edge i0_out[0]=indices[1]; i1_out[0]=indices[2]; } } ///////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////// Degenerate triangles //////////////////////////////////// static void DegenPrologue(STriInfo pTriInfos[], int piTriList_out[], const int iNrTrianglesIn, const int iTotTris) { int iNextGoodTriangleSearchIndex=-1; tbool bStillFindingGoodOnes; // locate quads with only one good triangle int t=0; while (t<(iTotTris-1)) { const int iFO_a = pTriInfos[t].iOrgFaceNumber; const int iFO_b = pTriInfos[t+1].iOrgFaceNumber; if (iFO_a==iFO_b) // this is a quad { const tbool bIsDeg_a = (pTriInfos[t].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; const tbool bIsDeg_b = (pTriInfos[t+1].iFlag&MARK_DEGENERATE)!=0 ? TTRUE : TFALSE; if ((bIsDeg_a^bIsDeg_b)!=0) { pTriInfos[t].iFlag |= QUAD_ONE_DEGEN_TRI; pTriInfos[t+1].iFlag |= QUAD_ONE_DEGEN_TRI; } t += 2; } else ++t; } // reorder list so all degen triangles are moved to the back // without reordering the good triangles iNextGoodTriangleSearchIndex = 1; t=0; bStillFindingGoodOnes = TTRUE; while (t (t+1)); // swap triangle t0 and t1 if (!bJustADegenerate) { int i=0; for (i=0; i<3; i++) { const int index = piTriList_out[t0*3+i]; piTriList_out[t0*3+i] = piTriList_out[t1*3+i]; piTriList_out[t1*3+i] = index; } { const STriInfo tri_info = pTriInfos[t0]; pTriInfos[t0] = pTriInfos[t1]; pTriInfos[t1] = tri_info; } } else bStillFindingGoodOnes = TFALSE; // this is not supposed to happen } if (bStillFindingGoodOnes) ++t; } assert(bStillFindingGoodOnes); // code will still work. assert(iNrTrianglesIn == t); } static void DegenEpilogue(STSpace psTspace[], STriInfo pTriInfos[], int piTriListIn[], const SMikkTSpaceContext * pContext, const int iNrTrianglesIn, const int iTotTris) { int t=0, i=0; // deal with degenerate triangles // punishment for degenerate triangles is O(N^2) for (t=iNrTrianglesIn; t 1800 # pragma warning(disable:4456) // hides previous local declaration #endif -#endif /** * Copyright (C) 2011 by Morten S. Mikkelsen * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ #include "weldmesh.h" #include #include #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) #include /* BSD-based OSes get their malloc stuff through here */ #else #include #endif static void MergeVertsFast(int * piCurNrUniqueVertices, int * piRemapTable, float * pfVertexDataOut, int * piVertexIDs, const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert, const int iL_in, const int iR_in, const int iChannelNum); int WeldMesh(int * piRemapTable, float * pfVertexDataOut, const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert) { int iUniqueVertices = 0, i=0; int * piVertexIDs = NULL; if(iNrVerticesIn<=0) return 0; iUniqueVertices = 0; piVertexIDs = (int *) malloc(sizeof(int)*iNrVerticesIn); if(piVertexIDs!=NULL) { for(i=0; i=0); } return iUniqueVertices; } static void MergeVertsFast(int * piCurNrUniqueVertices, int * piRemapTable, float * pfVertexDataOut, int * piVertexIDs, const float pfVertexDataIn[], const int iNrVerticesIn, const int iFloatsPerVert, const int iL_in, const int iR_in, const int iChannelNum) { const int iCount = iR_in-iL_in+1; int l=0; float fMin, fMax, fAvg; assert(iCount>0); // make bbox fMin = pfVertexDataIn[ piVertexIDs[iL_in]*iFloatsPerVert + iChannelNum]; fMax = fMin; for(l=(iL_in+1); l<=iR_in; l++) { const int index = piVertexIDs[l]*iFloatsPerVert + iChannelNum; const float fVal = pfVertexDataIn[index]; if(fMin>fVal) fMin=fVal; else if(fMax=fMax || iCount==1) { if((iChannelNum+1) == iFloatsPerVert || iCount==1) // we are done, weld by hand { int iUniqueNewVertices = 0; float * pfNewUniVertsOut = &pfVertexDataOut[ piCurNrUniqueVertices[0]*iFloatsPerVert ]; for(l=iL_in; l<=iR_in; l++) { const int index = piVertexIDs[l]*iFloatsPerVert; int iFound = 0; // didn't find copy yet. int l2=0; while(l2=iL_in && iL<=iR_in); index = piVertexIDs[iL]*iFloatsPerVert+iChannelNum; iReadyLeftSwap = !(pfVertexDataIn[index]=iL_in && iR<=iR_in); index = piVertexIDs[iR]*iFloatsPerVert+iChannelNum; iReadyRightSwap = pfVertexDataIn[index] 1800 # pragma warning(disable:4365) // signed unsigned mismatch # pragma warning(disable:4191) // unsafe conversion # pragma warning(disable:4820) // incorrect padding # pragma warning(disable:4668) // macro error # pragma warning(disable:4710) // function not inlined # pragma warning(disable:4711) // selected for automatic inline expansion -#endif # pragma comment(lib, "ws2_32.lib") #endif #ifdef __GNUC__ # pragma GCC diagnostic ignored "-Wunused-function" # ifndef __clang__ # pragma GCC diagnostic ignored "-Wunused-but-set-variable" # endif #endif #define NO_CGI #define NO_SSL #undef DEBUG #if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) // Fix undefined PF_INET on FreeBSD #include #endif // Copyright (c) 2004-2011 Sergey Lyubka // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #if defined(_WIN32) #define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2005 #else #define _XOPEN_SOURCE 600 // For flockfile() on Linux #define _LARGEFILE_SOURCE // Enable 64-bit file offsets #define __STDC_FORMAT_MACROS // wants this for C++ #endif #if defined(__SYMBIAN32__) #define NO_SSL // SSL is not supported #define NO_CGI // CGI is not supported #define PATH_MAX FILENAME_MAX #endif // __SYMBIAN32__ #ifndef _WIN32_WCE // Some ANSI #includes are not available on Windows CE #include #include #include #include #include #endif // !_WIN32_WCE #include #include #include #include #include #include #include #include #include #if defined(_WIN32) && !defined(__SYMBIAN32__) // Windows specific #define _WIN32_WINNT 0x0400 // To make it link in VS2005 #include #ifndef PATH_MAX #define PATH_MAX MAX_PATH #endif #ifndef _WIN32_WCE #include #include #include #else // _WIN32_WCE #include #define NO_CGI // WinCE has no pipes typedef long off_t; #define BUFSIZ 4096 #define errno GetLastError() #define strerror(x) _ultoa(x, (char *) _alloca(sizeof(x) *3 ), 10) #endif // _WIN32_WCE #define MAKEUQUAD(lo, hi) ((uint64_t)(((uint32_t)(lo)) | \ ((uint64_t)((uint32_t)(hi))) << 32)) #define RATE_DIFF 10000000 // 100 nsecs #define EPOCH_DIFF MAKEUQUAD(0xd53e8000, 0x019db1de) #define SYS2UNIX_TIME(lo, hi) \ (time_t) ((MAKEUQUAD((lo), (hi)) - EPOCH_DIFF) / RATE_DIFF) // Visual Studio 6 does not know __func__ or __FUNCTION__ // The rest of MS compilers use __FUNCTION__, not C99 __func__ // Also use _strtoui64 on modern M$ compilers #if defined(_MSC_VER) && _MSC_VER < 1300 #define STRX(x) #x #define STR(x) STRX(x) #define __func__ "line " STR(__LINE__) #define strtoull(x, y, z) strtoul(x, y, z) #define strtoll(x, y, z) strtol(x, y, z) #else #define __func__ __FUNCTION__ #define strtoull(x, y, z) _strtoui64(x, y, z) #define strtoll(x, y, z) _strtoi64(x, y, z) #endif // _MSC_VER #define ERRNO GetLastError() #define NO_SOCKLEN_T #define SSL_LIB "ssleay32.dll" #define CRYPTO_LIB "libeay32.dll" #define DIRSEP '\\' #define IS_DIRSEP_CHAR(c) ((c) == '/' || (c) == '\\') #define O_NONBLOCK 0 #if !defined(EWOULDBLOCK) #define EWOULDBLOCK WSAEWOULDBLOCK #endif // !EWOULDBLOCK #define _POSIX_ #define INT64_FMT "I64d" #define WINCDECL __cdecl #define SHUT_WR 1 #define snprintf _snprintf #define vsnprintf _vsnprintf #define sleep(x) Sleep((x) * 1000) #define pipe(x) _pipe(x, BUFSIZ, _O_BINARY) #define popen(x, y) _popen(x, y) #define pclose(x) _pclose(x) #define close(x) _close(x) #define dlsym(x,y) GetProcAddress((HINSTANCE) (x), (y)) #define RTLD_LAZY 0 #define fseeko(x, y, z) fseek((x), (y), (z)) #define fdopen(x, y) _fdopen((x), (y)) #define write(x, y, z) _write((x), (y), (unsigned) z) #define read(x, y, z) _read((x), (y), (unsigned) z) #define flockfile(x) (void) 0 #define funlockfile(x) (void) 0 #if !defined(fileno) #define fileno(x) _fileno(x) #endif // !fileno MINGW #defines fileno typedef HANDLE pthread_mutex_t; typedef struct {HANDLE signal, broadcast;} pthread_cond_t; typedef DWORD pthread_t; #define pid_t HANDLE // MINGW typedefs pid_t to int. Using #define here. #if _MSC_VER < 1900 struct timespec { long tv_nsec; long tv_sec; }; #endif static int pthread_mutex_lock(pthread_mutex_t *); static int pthread_mutex_unlock(pthread_mutex_t *); static FILE *mg_fopen(const char *path, const char *mode); #if defined(HAVE_STDINT) #include #else typedef unsigned int uint32_t; typedef unsigned short uint16_t; typedef unsigned __int64 uint64_t; typedef __int64 int64_t; #define INT64_MAX 9223372036854775807 #endif // HAVE_STDINT // POSIX dirent interface struct dirent { char d_name[PATH_MAX]; }; typedef struct DIR { HANDLE handle; WIN32_FIND_DATAW info; struct dirent result; } DIR; #else // UNIX specific #include #include #include #include #include #include #include #include #include #include #include #include #if !defined(NO_SSL_DL) && !defined(NO_SSL) #include #endif #include #if defined(__MACH__) #define SSL_LIB "libssl.dylib" #define CRYPTO_LIB "libcrypto.dylib" #else #if !defined(SSL_LIB) #define SSL_LIB "libssl.so" #endif #if !defined(CRYPTO_LIB) #define CRYPTO_LIB "libcrypto.so" #endif #endif #define DIRSEP '/' #define IS_DIRSEP_CHAR(c) ((c) == '/') #ifndef O_BINARY #define O_BINARY 0 #endif // O_BINARY #define closesocket(a) close(a) #define mg_fopen(x, y) fopen(x, y) #define mg_mkdir(x, y) mkdir(x, y) #define mg_remove(x) remove(x) #define mg_rename(x, y) rename(x, y) #define ERRNO errno #define INVALID_SOCKET (-1) #define INT64_FMT PRId64 typedef int SOCKET; #define WINCDECL #endif // End of Windows and UNIX specific includes #include "mongoose.h" #define MONGOOSE_VERSION "3.1" #define PASSWORDS_FILE_NAME ".htpasswd" #define CGI_ENVIRONMENT_SIZE 4096 #define MAX_CGI_ENVIR_VARS 64 #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) #ifdef _WIN32 static pthread_t pthread_self(void) { return GetCurrentThreadId(); } #endif // _WIN32 #if defined(DEBUG) #define DEBUG_TRACE(x) do { \ flockfile(stdout); \ printf("*** %lu.%p.%s.%d: ", \ (unsigned long) time(NULL), (void *) pthread_self(), \ __func__, __LINE__); \ printf x; \ putchar('\n'); \ fflush(stdout); \ funlockfile(stdout); \ } while (0) #else #define DEBUG_TRACE(x) #endif // DEBUG // Darwin prior to 7.0 and Win32 do not have socklen_t #ifdef NO_SOCKLEN_T typedef int socklen_t; #endif // NO_SOCKLEN_T typedef void * (*mg_thread_func_t)(void *); static const char *http_500_error = "Internal Server Error"; // Snatched from OpenSSL includes. I put the prototypes here to be independent // from the OpenSSL source installation. Having this, mongoose + SSL can be // built on any system with binary SSL libraries installed. typedef struct ssl_st SSL; typedef struct ssl_method_st SSL_METHOD; typedef struct ssl_ctx_st SSL_CTX; #define SSL_ERROR_WANT_READ 2 #define SSL_ERROR_WANT_WRITE 3 #define SSL_FILETYPE_PEM 1 #define CRYPTO_LOCK 1 #if defined(NO_SSL_DL) extern void SSL_free(SSL *); extern int SSL_accept(SSL *); extern int SSL_connect(SSL *); extern int SSL_read(SSL *, void *, int); extern int SSL_write(SSL *, const void *, int); extern int SSL_get_error(const SSL *, int); extern int SSL_set_fd(SSL *, int); extern SSL *SSL_new(SSL_CTX *); extern SSL_CTX *SSL_CTX_new(SSL_METHOD *); extern SSL_METHOD *SSLv23_server_method(void); extern int SSL_library_init(void); extern void SSL_load_error_strings(void); extern int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int); extern int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int); extern int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *); extern void SSL_CTX_set_default_passwd_cb(SSL_CTX *, mg_callback_t); extern void SSL_CTX_free(SSL_CTX *); extern unsigned long ERR_get_error(void); extern char *ERR_error_string(unsigned long, char *); extern int CRYPTO_num_locks(void); extern void CRYPTO_set_locking_callback(void (*)(int, int, const char *, int)); extern void CRYPTO_set_id_callback(unsigned long (*)(void)); #else // Dynamically loaded SSL functionality struct ssl_func { const char *name; // SSL function name void (*ptr)(void); // Function pointer }; #define SSL_free (* (void (*)(SSL *)) ssl_sw[0].ptr) #define SSL_accept (* (int (*)(SSL *)) ssl_sw[1].ptr) #define SSL_connect (* (int (*)(SSL *)) ssl_sw[2].ptr) #define SSL_read (* (int (*)(SSL *, void *, int)) ssl_sw[3].ptr) #define SSL_write (* (int (*)(SSL *, const void *,int)) ssl_sw[4].ptr) #define SSL_get_error (* (int (*)(SSL *, int)) ssl_sw[5].ptr) #define SSL_set_fd (* (int (*)(SSL *, SOCKET)) ssl_sw[6].ptr) #define SSL_new (* (SSL * (*)(SSL_CTX *)) ssl_sw[7].ptr) #define SSL_CTX_new (* (SSL_CTX * (*)(SSL_METHOD *)) ssl_sw[8].ptr) #define SSLv23_server_method (* (SSL_METHOD * (*)(void)) ssl_sw[9].ptr) #define SSL_library_init (* (int (*)(void)) ssl_sw[10].ptr) #define SSL_CTX_use_PrivateKey_file (* (int (*)(SSL_CTX *, \ const char *, int)) ssl_sw[11].ptr) #define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \ const char *, int)) ssl_sw[12].ptr) #define SSL_CTX_set_default_passwd_cb \ (* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr) #define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr) #define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr) #define SSL_CTX_use_certificate_chain_file \ (* (int (*)(SSL_CTX *, const char *)) ssl_sw[16].ptr) #define CRYPTO_num_locks (* (int (*)(void)) crypto_sw[0].ptr) #define CRYPTO_set_locking_callback \ (* (void (*)(void (*)(int, int, const char *, int))) crypto_sw[1].ptr) #define CRYPTO_set_id_callback \ (* (void (*)(unsigned long (*)(void))) crypto_sw[2].ptr) #define ERR_get_error (* (unsigned long (*)(void)) crypto_sw[3].ptr) #define ERR_error_string (* (char * (*)(unsigned long,char *)) crypto_sw[4].ptr) // set_ssl_option() function updates this array. // It loads SSL library dynamically and changes NULLs to the actual addresses // of respective functions. The macros above (like SSL_connect()) are really // just calling these functions indirectly via the pointer. static struct ssl_func ssl_sw[] = { {"SSL_free", NULL}, {"SSL_accept", NULL}, {"SSL_connect", NULL}, {"SSL_read", NULL}, {"SSL_write", NULL}, {"SSL_get_error", NULL}, {"SSL_set_fd", NULL}, {"SSL_new", NULL}, {"SSL_CTX_new", NULL}, {"SSLv23_server_method", NULL}, {"SSL_library_init", NULL}, {"SSL_CTX_use_PrivateKey_file", NULL}, {"SSL_CTX_use_certificate_file",NULL}, {"SSL_CTX_set_default_passwd_cb",NULL}, {"SSL_CTX_free", NULL}, {"SSL_load_error_strings", NULL}, {"SSL_CTX_use_certificate_chain_file", NULL}, {NULL, NULL} }; // Similar array as ssl_sw. These functions could be located in different lib. static struct ssl_func crypto_sw[] = { {"CRYPTO_num_locks", NULL}, {"CRYPTO_set_locking_callback", NULL}, {"CRYPTO_set_id_callback", NULL}, {"ERR_get_error", NULL}, {"ERR_error_string", NULL}, {NULL, NULL} }; #endif // NO_SSL_DL static const char *month_names[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; // Unified socket address. For IPv6 support, add IPv6 address structure // in the union u. struct usa { socklen_t len; union { struct sockaddr sa; struct sockaddr_in sin; } u; }; // Describes a string (chunk of memory). struct vec { const char *ptr; size_t len; }; // Structure used by mg_stat() function. Uses 64 bit file length. struct mgstat { int is_directory; // Directory marker int64_t size; // File size time_t mtime; // Modification time }; // Describes listening socket, or socket which was accept()-ed by the master // thread and queued for future handling by the worker thread. struct socket { struct socket *next; // Linkage SOCKET sock; // Listening socket struct usa lsa; // Local socket address struct usa rsa; // Remote socket address int is_ssl; // Is socket SSL-ed int is_proxy; }; enum { CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER, PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, ACCESS_LOG_FILE, SSL_CHAIN_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE, GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST, MAX_REQUEST_SIZE, EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE, NUM_THREADS, RUN_AS_USER, NUM_OPTIONS }; static const char *config_options[] = { "C", "cgi_extensions", ".cgi,.pl,.php", "E", "cgi_environment", NULL, "G", "put_delete_passwords_file", NULL, "I", "cgi_interpreter", NULL, "P", "protect_uri", NULL, "R", "authentication_domain", "mydomain.com", "S", "ssi_extensions", ".shtml,.shtm", "a", "access_log_file", NULL, "c", "ssl_chain_file", NULL, "d", "enable_directory_listing", "yes", "e", "error_log_file", NULL, "g", "global_passwords_file", NULL, "i", "index_files", "index.html,index.htm,index.cgi", "k", "enable_keep_alive", "no", "l", "access_control_list", NULL, "M", "max_request_size", "16384", "m", "extra_mime_types", NULL, "p", "listening_ports", "8080", "r", "document_root", ".", "s", "ssl_certificate", NULL, "t", "num_threads", "10", "u", "run_as_user", NULL, NULL }; #define ENTRIES_PER_CONFIG_OPTION 3 struct mg_context { volatile int stop_flag; // Should we stop event loop SSL_CTX *ssl_ctx; // SSL context char *config[NUM_OPTIONS]; // Mongoose configuration parameters mg_callback_t user_callback; // User-defined callback function void *user_data; // User-defined data struct socket *listening_sockets; volatile int num_threads; // Number of threads pthread_mutex_t mutex; // Protects (max|num)_threads pthread_cond_t cond; // Condvar for tracking workers terminations struct socket queue[20]; // Accepted sockets volatile int sq_head; // Head of the socket queue volatile int sq_tail; // Tail of the socket queue pthread_cond_t sq_full; // Singaled when socket is produced pthread_cond_t sq_empty; // Signaled when socket is consumed }; struct mg_connection { struct mg_connection *peer; // Remote target in proxy mode struct mg_request_info request_info; struct mg_context *ctx; SSL *ssl; // SSL descriptor struct socket client; // Connected client time_t birth_time; // Time connection was accepted int64_t num_bytes_sent; // Total bytes sent to client int64_t content_len; // Content-Length header value int64_t consumed_content; // How many bytes of content is already read char *buf; // Buffer for received data int buf_size; // Buffer size int request_len; // Size of the request + headers in a buffer int data_len; // Total size of data in a buffer }; const char **mg_get_valid_option_names(void) { return config_options; } static void *call_user(struct mg_connection *conn, enum mg_event event) { conn->request_info.user_data = conn->ctx->user_data; return conn->ctx->user_callback == NULL ? NULL : conn->ctx->user_callback(event, conn, &conn->request_info); } static int get_option_index(const char *name) { int i; for (i = 0; config_options[i] != NULL; i += ENTRIES_PER_CONFIG_OPTION) { if (strcmp(config_options[i], name) == 0 || strcmp(config_options[i + 1], name) == 0) { return i / ENTRIES_PER_CONFIG_OPTION; } } return -1; } const char *mg_get_option(const struct mg_context *ctx, const char *name) { int i; if ((i = get_option_index(name)) == -1) { return NULL; } else if (ctx->config[i] == NULL) { return ""; } else { return ctx->config[i]; } } // Print error message to the opened error log stream. static void cry(struct mg_connection *conn, const char *fmt, ...) { char buf[BUFSIZ]; va_list ap; FILE *fp; time_t timestamp; va_start(ap, fmt); (void) vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); // Do not lock when getting the callback value, here and below. // I suppose this is fine, since function cannot disappear in the // same way string option can. conn->request_info.log_message = buf; if (call_user(conn, MG_EVENT_LOG) == NULL) { fp = conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL : mg_fopen(conn->ctx->config[ERROR_LOG_FILE], "a+"); if (fp != NULL) { flockfile(fp); timestamp = time(NULL); (void) fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp, inet_ntoa(conn->client.rsa.u.sin.sin_addr)); if (conn->request_info.request_method != NULL) { (void) fprintf(fp, "%s %s: ", conn->request_info.request_method, conn->request_info.uri); } (void) fprintf(fp, "%s", buf); fputc('\n', fp); funlockfile(fp); if (fp != stderr) { fclose(fp); } } } conn->request_info.log_message = NULL; } // Return OpenSSL error message static const char *ssl_error(void) { unsigned long err; err = ERR_get_error(); return err == 0 ? "" : ERR_error_string(err, NULL); } // Return fake connection structure. Used for logging, if connection // is not applicable at the moment of logging. static struct mg_connection *fc(struct mg_context *ctx) { static struct mg_connection fake_connection; fake_connection.ctx = ctx; return &fake_connection; } const char *mg_version(void) { return MONGOOSE_VERSION; } static void mg_strlcpy(char *dst, const char *src, size_t n) { for (; *src != '\0' && n > 1; n--) { *dst++ = *src++; } *dst = '\0'; } static int lowercase(const char *s) { return tolower(* (const unsigned char *) s); } static int mg_strncasecmp(const char *s1, const char *s2, size_t len) { int diff = 0; if (len > 0) do { diff = lowercase(s1++) - lowercase(s2++); } while (diff == 0 && s1[-1] != '\0' && --len > 0); return diff; } static int mg_strcasecmp(const char *s1, const char *s2) { int diff; do { diff = lowercase(s1++) - lowercase(s2++); } while (diff == 0 && s1[-1] != '\0'); return diff; } static char * mg_strndup(const char *ptr, size_t len) { char *p; if ((p = (char *) malloc(len + 1)) != NULL) { mg_strlcpy(p, ptr, len + 1); } return p; } static char * mg_strdup(const char *str) { return mg_strndup(str, strlen(str)); } // Like snprintf(), but never returns negative value, or the value // that is larger than a supplied buffer. // Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability // in his audit report. static int mg_vsnprintf(struct mg_connection *conn, char *buf, size_t buflen, const char *fmt, va_list ap) { int n; if (buflen == 0) return 0; n = vsnprintf(buf, buflen, fmt, ap); if (n < 0) { cry(conn, "vsnprintf error"); n = 0; } else if (n >= (int) buflen) { cry(conn, "truncating vsnprintf buffer: [%.*s]", n > 200 ? 200 : n, buf); n = (int) buflen - 1; } buf[n] = '\0'; return n; } static int mg_snprintf(struct mg_connection *conn, char *buf, size_t buflen, const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = mg_vsnprintf(conn, buf, buflen, fmt, ap); va_end(ap); return n; } // Skip the characters until one of the delimiters characters found. // 0-terminate resulting word. Skip the delimiter and following whitespaces if any. // Advance pointer to buffer to the next word. Return found 0-terminated word. // Delimiters can be quoted with quotechar. static char *skip_quoted(char **buf, const char *delimiters, const char *whitespace, char quotechar) { char *p, *begin_word, *end_word, *end_whitespace; begin_word = *buf; end_word = begin_word + strcspn(begin_word, delimiters); // Check for quotechar if (end_word > begin_word) { p = end_word - 1; while (*p == quotechar) { // If there is anything beyond end_word, copy it if (*end_word == '\0') { *p = '\0'; break; } else { size_t end_off = strcspn(end_word + 1, delimiters); memmove (p, end_word, end_off + 1); p += end_off; // p must correspond to end_word - 1 end_word += end_off + 1; } } for (p++; p < end_word; p++) { *p = '\0'; } } if (*end_word == '\0') { *buf = end_word; } else { end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace); for (p = end_word; p < end_whitespace; p++) { *p = '\0'; } *buf = end_whitespace; } return begin_word; } // Simplified version of skip_quoted without quote char // and whitespace == delimiters static char *skip(char **buf, const char *delimiters) { return skip_quoted(buf, delimiters, delimiters, 0); } // Return HTTP header value, or NULL if not found. static const char *get_header(const struct mg_request_info *ri, const char *name) { int i; for (i = 0; i < ri->num_headers; i++) if (!mg_strcasecmp(name, ri->http_headers[i].name)) return ri->http_headers[i].value; return NULL; } const char *mg_get_header(const struct mg_connection *conn, const char *name) { return get_header(&conn->request_info, name); } // A helper function for traversing comma separated list of values. // It returns a list pointer shifted to the next value, of NULL if the end // of the list found. // Value is stored in val vector. If value has form "x=y", then eq_val // vector is initialized to point to the "y" part, and val vector length // is adjusted to point only to "x". static const char *next_option(const char *list, struct vec *val, struct vec *eq_val) { if (list == NULL || *list == '\0') { // End of the list list = NULL; } else { val->ptr = list; if ((list = strchr(val->ptr, ',')) != NULL) { // Comma found. Store length and shift the list ptr val->len = list - val->ptr; list++; } else { // This value is the last one list = val->ptr + strlen(val->ptr); val->len = list - val->ptr; } if (eq_val != NULL) { // Value has form "x=y", adjust pointers and lengths // so that val points to "x", and eq_val points to "y". eq_val->len = 0; eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len); if (eq_val->ptr != NULL) { eq_val->ptr++; // Skip over '=' character eq_val->len = val->ptr + val->len - eq_val->ptr; val->len = (eq_val->ptr - val->ptr) - 1; } } } return list; } static int match_extension(const char *path, const char *ext_list) { struct vec ext_vec; size_t path_len; path_len = strlen(path); while ((ext_list = next_option(ext_list, &ext_vec, NULL)) != NULL) if (ext_vec.len < path_len && mg_strncasecmp(path + path_len - ext_vec.len, ext_vec.ptr, ext_vec.len) == 0) return 1; return 0; } // HTTP 1.1 assumes keep alive if "Connection:" header is not set // This function must tolerate situations when connection info is not // set up, for example if request parsing failed. static int should_keep_alive(const struct mg_connection *conn) { const char *http_version = conn->request_info.http_version; const char *header = mg_get_header(conn, "Connection"); return (header == NULL && http_version && !strcmp(http_version, "1.1")) || (header != NULL && !mg_strcasecmp(header, "keep-alive")); } static const char *suggest_connection_header(const struct mg_connection *conn) { return should_keep_alive(conn) ? "keep-alive" : "close"; } static void send_http_error(struct mg_connection *conn, int status, const char *reason, const char *fmt, ...) { char buf[BUFSIZ]; va_list ap; int len; conn->request_info.status_code = status; if (call_user(conn, MG_HTTP_ERROR) == NULL) { buf[0] = '\0'; len = 0; // Errors 1xx, 204 and 304 MUST NOT send a body if (status > 199 && status != 204 && status != 304) { len = mg_snprintf(conn, buf, sizeof(buf), "Error %d: %s", status, reason); cry(conn, "%s", buf); buf[len++] = '\n'; va_start(ap, fmt); len += mg_vsnprintf(conn, buf + len, sizeof(buf) - len, fmt, ap); va_end(ap); } DEBUG_TRACE(("[%s]", buf)); mg_printf(conn, "HTTP/1.1 %d %s\r\n" "Content-Type: text/plain\r\n" "Content-Length: %d\r\n" "Connection: %s\r\n\r\n", status, reason, len, suggest_connection_header(conn)); conn->num_bytes_sent += mg_printf(conn, "%s", buf); } } #if defined(_WIN32) && !defined(__SYMBIAN32__) static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) { unused = NULL; *mutex = CreateMutex(NULL, FALSE, NULL); return *mutex == NULL ? -1 : 0; } static int pthread_mutex_destroy(pthread_mutex_t *mutex) { return CloseHandle(*mutex) == 0 ? -1 : 0; } static int pthread_mutex_lock(pthread_mutex_t *mutex) { return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1; } static int pthread_mutex_unlock(pthread_mutex_t *mutex) { return ReleaseMutex(*mutex) == 0 ? -1 : 0; } static int pthread_cond_init(pthread_cond_t *cv, const void *unused) { unused = NULL; cv->signal = CreateEvent(NULL, FALSE, FALSE, NULL); cv->broadcast = CreateEvent(NULL, TRUE, FALSE, NULL); return cv->signal != NULL && cv->broadcast != NULL ? 0 : -1; } static int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex) { HANDLE handles[] = {cv->signal, cv->broadcast}; ReleaseMutex(*mutex); WaitForMultipleObjects(2, handles, FALSE, INFINITE); return WaitForSingleObject(*mutex, INFINITE) == WAIT_OBJECT_0? 0 : -1; } static int pthread_cond_signal(pthread_cond_t *cv) { return SetEvent(cv->signal) == 0 ? -1 : 0; } static int pthread_cond_broadcast(pthread_cond_t *cv) { // Implementation with PulseEvent() has race condition, see // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html return PulseEvent(cv->broadcast) == 0 ? -1 : 0; } static int pthread_cond_destroy(pthread_cond_t *cv) { return CloseHandle(cv->signal) && CloseHandle(cv->broadcast) ? 0 : -1; } // For Windows, change all slashes to backslashes in path names. static void change_slashes_to_backslashes(char *path) { int i; for (i = 0; path[i] != '\0'; i++) { if (path[i] == '/') path[i] = '\\'; // i > 0 check is to preserve UNC paths, like \\server\file.txt if (path[i] == '\\' && i > 0) while (path[i + 1] == '\\' || path[i + 1] == '/') (void) memmove(path + i + 1, path + i + 2, strlen(path + i + 1)); } } // Encode 'path' which is assumed UTF-8 string, into UNICODE string. // wbuf and wbuf_len is a target buffer and its length. static void to_unicode(const char *path, wchar_t *wbuf, size_t wbuf_len) { char buf[PATH_MAX], buf2[PATH_MAX], *p; mg_strlcpy(buf, path, sizeof(buf)); change_slashes_to_backslashes(buf); // Point p to the end of the file name p = buf + strlen(buf) - 1; // Trim trailing backslash character while (p > buf && *p == '\\' && p[-1] != ':') { *p-- = '\0'; } // Protect from CGI code disclosure. // This is very nasty hole. Windows happily opens files with // some garbage in the end of file name. So fopen("a.cgi ", "r") // actually opens "a.cgi", and does not return an error! if (*p == 0x20 || // No space at the end (*p == 0x2e && p > buf) || // No '.' but allow '.' as full path *p == 0x2b || // No '+' (*p & ~0x7f)) { // And generally no non-ascii chars (void) fprintf(stderr, "Rejecting suspicious path: [%s]", buf); wbuf[0] = L'\0'; } else { // Convert to Unicode and back. If doubly-converted string does not // match the original, something is fishy, reject. MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int) wbuf_len); WideCharToMultiByte(CP_UTF8, 0, wbuf, (int) wbuf_len, buf2, sizeof(buf2), NULL, NULL); if (strcmp(buf, buf2) != 0) { wbuf[0] = L'\0'; } } } #if defined(_WIN32_WCE) static time_t time(time_t *ptime) { time_t t; SYSTEMTIME st; FILETIME ft; GetSystemTime(&st); SystemTimeToFileTime(&st, &ft); t = SYS2UNIX_TIME(ft.dwLowDateTime, ft.dwHighDateTime); if (ptime != NULL) { *ptime = t; } return t; } static struct tm *localtime(const time_t *ptime, struct tm *ptm) { int64_t t = ((int64_t) *ptime) * RATE_DIFF + EPOCH_DIFF; FILETIME ft, lft; SYSTEMTIME st; TIME_ZONE_INFORMATION tzinfo; if (ptm == NULL) { return NULL; } * (int64_t *) &ft = t; FileTimeToLocalFileTime(&ft, &lft); FileTimeToSystemTime(&lft, &st); ptm->tm_year = st.wYear - 1900; ptm->tm_mon = st.wMonth - 1; ptm->tm_wday = st.wDayOfWeek; ptm->tm_mday = st.wDay; ptm->tm_hour = st.wHour; ptm->tm_min = st.wMinute; ptm->tm_sec = st.wSecond; ptm->tm_yday = 0; // hope nobody uses this ptm->tm_isdst = GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_DAYLIGHT ? 1 : 0; return ptm; } static struct tm *gmtime(const time_t *ptime, struct tm *ptm) { // FIXME(lsm): fix this. return localtime(ptime, ptm); } static size_t strftime(char *dst, size_t dst_size, const char *fmt, const struct tm *tm) { (void) snprintf(dst, dst_size, "implement strftime() for WinCE"); return 0; } #endif static int mg_rename(const char* oldname, const char* newname) { wchar_t woldbuf[PATH_MAX]; wchar_t wnewbuf[PATH_MAX]; to_unicode(oldname, woldbuf, ARRAY_SIZE(woldbuf)); to_unicode(newname, wnewbuf, ARRAY_SIZE(wnewbuf)); return MoveFileW(woldbuf, wnewbuf) ? 0 : -1; } static FILE *mg_fopen(const char *path, const char *mode) { wchar_t wbuf[PATH_MAX], wmode[20]; to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode)); return _wfopen(wbuf, wmode); } static int mg_stat(const char *path, struct mgstat *stp) { int ok = -1; // Error wchar_t wbuf[PATH_MAX]; WIN32_FILE_ATTRIBUTE_DATA info; to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); if (GetFileAttributesExW(wbuf, GetFileExInfoStandard, &info) != 0) { stp->size = MAKEUQUAD(info.nFileSizeLow, info.nFileSizeHigh); stp->mtime = SYS2UNIX_TIME(info.ftLastWriteTime.dwLowDateTime, info.ftLastWriteTime.dwHighDateTime); stp->is_directory = info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY; ok = 0; // Success } return ok; } static int mg_remove(const char *path) { wchar_t wbuf[PATH_MAX]; to_unicode(path, wbuf, ARRAY_SIZE(wbuf)); return DeleteFileW(wbuf) ? 0 : -1; } static int mg_mkdir(const char *path, int mode) { char buf[PATH_MAX]; wchar_t wbuf[PATH_MAX]; mode = 0; // Unused mg_strlcpy(buf, path, sizeof(buf)); change_slashes_to_backslashes(buf); (void) MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf)); return CreateDirectoryW(wbuf, NULL) ? 0 : -1; } // Implementation of POSIX opendir/closedir/readdir for Windows. static DIR * opendir(const char *name) { DIR *dir = NULL; wchar_t wpath[PATH_MAX]; DWORD attrs; if (name == NULL) { SetLastError(ERROR_BAD_ARGUMENTS); } else if ((dir = (DIR *) malloc(sizeof(*dir))) == NULL) { SetLastError(ERROR_NOT_ENOUGH_MEMORY); } else { to_unicode(name, wpath, ARRAY_SIZE(wpath)); attrs = GetFileAttributesW(wpath); if (attrs != 0xFFFFFFFF && ((attrs & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY)) { (void) wcscat(wpath, L"\\*"); dir->handle = FindFirstFileW(wpath, &dir->info); dir->result.d_name[0] = '\0'; } else { free(dir); dir = NULL; } } return dir; } static int closedir(DIR *dir) { int result = 0; if (dir != NULL) { if (dir->handle != INVALID_HANDLE_VALUE) result = FindClose(dir->handle) ? 0 : -1; free(dir); } else { result = -1; SetLastError(ERROR_BAD_ARGUMENTS); } return result; } struct dirent * readdir(DIR *dir) { struct dirent *result = 0; if (dir) { if (dir->handle != INVALID_HANDLE_VALUE) { result = &dir->result; (void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1, result->d_name, sizeof(result->d_name), NULL, NULL); if (!FindNextFileW(dir->handle, &dir->info)) { (void) FindClose(dir->handle); dir->handle = INVALID_HANDLE_VALUE; } } else { SetLastError(ERROR_FILE_NOT_FOUND); } } else { SetLastError(ERROR_BAD_ARGUMENTS); } return result; } #define set_close_on_exec(fd) // No FD_CLOEXEC on Windows static int start_thread(struct mg_context *ctx, mg_thread_func_t f, void *p) { return _beginthread((void (__cdecl *)(void *)) f, 0, p) == -1L ? -1 : 0; } static HANDLE dlopen(const char *dll_name, int flags) { wchar_t wbuf[PATH_MAX]; flags = 0; // Unused to_unicode(dll_name, wbuf, ARRAY_SIZE(wbuf)); return LoadLibraryW(wbuf); } #if !defined(NO_CGI) #define SIGKILL 0 static int kill(pid_t pid, int sig_num) { (void) TerminateProcess(pid, sig_num); (void) CloseHandle(pid); return 0; } static pid_t spawn_process(struct mg_connection *conn, const char *prog, char *envblk, char *envp[], int fd_stdin, int fd_stdout, const char *dir) { HANDLE me; char *p, *interp, cmdline[PATH_MAX], buf[PATH_MAX]; FILE *fp; STARTUPINFOA si; PROCESS_INFORMATION pi; envp = NULL; // Unused (void) memset(&si, 0, sizeof(si)); (void) memset(&pi, 0, sizeof(pi)); // TODO(lsm): redirect CGI errors to the error log file si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; si.wShowWindow = SW_HIDE; me = GetCurrentProcess(); (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me, &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS); (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me, &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS); // If CGI file is a script, try to read the interpreter line interp = conn->ctx->config[CGI_INTERPRETER]; if (interp == NULL) { buf[2] = '\0'; mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%c%s", dir, DIRSEP, prog); if ((fp = fopen(cmdline, "r")) != NULL) { (void) fgets(buf, sizeof(buf), fp); if (buf[0] != '#' || buf[1] != '!') { // First line does not start with "#!". Do not set interpreter. buf[2] = '\0'; } else { // Trim whitespaces in interpreter name for (p = &buf[strlen(buf) - 1]; p > buf && isspace(*p); p--) { *p = '\0'; } } (void) fclose(fp); } interp = buf + 2; } (void) mg_snprintf(conn, cmdline, sizeof(cmdline), "%s%s%s%c%s", interp, interp[0] == '\0' ? "" : " ", dir, DIRSEP, prog); DEBUG_TRACE(("Running [%s]", cmdline)); if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, envblk, dir, &si, &pi) == 0) { cry(conn, "%s: CreateProcess(%s): %d", __func__, cmdline, ERRNO); pi.hProcess = (pid_t) -1; } else { (void) close(fd_stdin); (void) close(fd_stdout); } (void) CloseHandle(si.hStdOutput); (void) CloseHandle(si.hStdInput); (void) CloseHandle(pi.hThread); return (pid_t) pi.hProcess; } #endif // !NO_CGI static int set_non_blocking_mode(SOCKET sock) { unsigned long on = 1; return ioctlsocket(sock, FIONBIO, &on); } #else static int mg_stat(const char *path, struct mgstat *stp) { struct stat st; int ok; if (stat(path, &st) == 0) { ok = 0; stp->size = st.st_size; stp->mtime = st.st_mtime; stp->is_directory = S_ISDIR(st.st_mode); } else { ok = -1; } return ok; } static void set_close_on_exec(int fd) { (void) fcntl(fd, F_SETFD, FD_CLOEXEC); } static int start_thread(struct mg_context *ctx, mg_thread_func_t func, void *param) { pthread_t thread_id; pthread_attr_t attr; int retval; (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // TODO(lsm): figure out why mongoose dies on Linux if next line is enabled // (void) pthread_attr_setstacksize(&attr, sizeof(struct mg_connection) * 5); if ((retval = pthread_create(&thread_id, &attr, func, param)) != 0) { cry(fc(ctx), "%s: %s", __func__, strerror(retval)); } return retval; } #ifndef NO_CGI static pid_t spawn_process(struct mg_connection *conn, const char *prog, char *envblk, char *envp[], int fd_stdin, int fd_stdout, const char *dir) { pid_t pid; const char *interp; envblk = NULL; // Unused if ((pid = fork()) == -1) { // Parent send_http_error(conn, 500, http_500_error, "fork(): %s", strerror(ERRNO)); } else if (pid == 0) { // Child if (chdir(dir) != 0) { cry(conn, "%s: chdir(%s): %s", __func__, dir, strerror(ERRNO)); } else if (dup2(fd_stdin, 0) == -1) { cry(conn, "%s: dup2(%d, 0): %s", __func__, fd_stdin, strerror(ERRNO)); } else if (dup2(fd_stdout, 1) == -1) { cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO)); } else { (void) dup2(fd_stdout, 2); (void) close(fd_stdin); (void) close(fd_stdout); // Execute CGI program. No need to lock: new process interp = conn->ctx->config[CGI_INTERPRETER]; if (interp == NULL) { (void) execle(prog, prog, NULL, envp); cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO)); } else { (void) execle(interp, interp, prog, NULL, envp); cry(conn, "%s: execle(%s %s): %s", __func__, interp, prog, strerror(ERRNO)); } } exit(EXIT_FAILURE); } else { // Parent. Close stdio descriptors (void) close(fd_stdin); (void) close(fd_stdout); } return pid; } #endif // !NO_CGI static int set_non_blocking_mode(SOCKET sock) { int flags; flags = fcntl(sock, F_GETFL, 0); (void) fcntl(sock, F_SETFL, flags | O_NONBLOCK); return 0; } #endif // _WIN32 // Write data to the IO channel - opened file descriptor, socket or SSL // descriptor. Return number of bytes written. static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, int64_t len) { int64_t sent; int n, k; sent = 0; while (sent < len) { // How many bytes we send in this iteration k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent); if (ssl != NULL) { n = SSL_write(ssl, buf + sent, k); } else if (fp != NULL) { n = fwrite(buf + sent, 1, (size_t)k, fp); if (ferror(fp)) n = -1; } else { n = send(sock, buf + sent, (size_t)k, 0); } if (n < 0) break; sent += n; } return sent; } // Read from IO channel - opened file descriptor, socket, or SSL descriptor. // Return number of bytes read. static int pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len) { int nread; if (ssl != NULL) { nread = SSL_read(ssl, buf, len); } else if (fp != NULL) { // Use read() instead of fread(), because if we're reading from the CGI // pipe, fread() may block until IO buffer is filled up. We cannot afford // to block and must pass all read bytes immediately to the client. nread = read(fileno(fp), buf, (size_t) len); if (ferror(fp)) nread = -1; } else { nread = recv(sock, buf, (size_t) len, 0); } return nread; } int mg_read(struct mg_connection *conn, void *buf, size_t len) { int n, buffered_len, nread; const char *buffered; assert((conn->content_len == -1 && conn->consumed_content == 0) || conn->consumed_content <= conn->content_len); DEBUG_TRACE(("%p %zu %lld %lld", buf, len, conn->content_len, conn->consumed_content)); nread = 0; if (conn->consumed_content < conn->content_len) { // Adjust number of bytes to read. int64_t to_read = conn->content_len - conn->consumed_content; if (to_read < (int64_t) len) { len = (int) to_read; } // How many bytes of data we have buffered in the request buffer? buffered = conn->buf + conn->request_len + conn->consumed_content; buffered_len = conn->data_len - conn->request_len; assert(buffered_len >= 0); // Return buffered data back if we haven't done that yet. if (conn->consumed_content < (int64_t) buffered_len) { buffered_len -= (int) conn->consumed_content; if (len < (size_t) buffered_len) { buffered_len = len; } memcpy(buf, buffered, (size_t)buffered_len); len -= buffered_len; buf = (char *) buf + buffered_len; conn->consumed_content += buffered_len; nread = buffered_len; } // We have returned all buffered data. Read new data from the remote socket. while (len > 0) { n = pull(NULL, conn->client.sock, conn->ssl, (char *) buf, (int) len); if (n <= 0) { break; } buf = (char *) buf + n; conn->consumed_content += n; nread += n; len -= n; } } return nread; } int mg_write(struct mg_connection *conn, const void *buf, size_t len) { return (int) push(NULL, conn->client.sock, conn->ssl, (const char *) buf, (int64_t) len); } int mg_printf(struct mg_connection *conn, const char *fmt, ...) { char buf[BUFSIZ]; int len; va_list ap; va_start(ap, fmt); len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap); va_end(ap); return mg_write(conn, buf, (size_t)len); } // URL-decode input buffer into destination buffer. // 0-terminate the destination buffer. Return the length of decoded data. // form-url-encoded data differs from URI encoding in a way that it // uses '+' as character for space, see RFC 1866 section 8.2.1 // http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt static size_t url_decode(const char *src, size_t src_len, char *dst, size_t dst_len, int is_form_url_encoded) { size_t i, j; int a, b; #define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { if (src[i] == '%' && isxdigit(* (const unsigned char *) (src + i + 1)) && isxdigit(* (const unsigned char *) (src + i + 2))) { a = tolower(* (const unsigned char *) (src + i + 1)); b = tolower(* (const unsigned char *) (src + i + 2)); dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b)); i += 2; } else if (is_form_url_encoded && src[i] == '+') { dst[j] = ' '; } else { dst[j] = src[i]; } } dst[j] = '\0'; // Null-terminate the destination return j; } // Scan given buffer and fetch the value of the given variable. // It can be specified in query string, or in the POST data. // Return NULL if the variable not found, or allocated 0-terminated value. // It is caller's responsibility to free the returned value. int mg_get_var(const char *buf, size_t buf_len, const char *name, char *dst, size_t dst_len) { const char *p, *e, *s; size_t name_len, len; name_len = strlen(name); e = buf + buf_len; len = -1; dst[0] = '\0'; // buf is "var1=val1&var2=val2...". Find variable first for (p = buf; p != NULL && p + name_len < e; p++) { if ((p == buf || p[-1] == '&') && p[name_len] == '=' && !mg_strncasecmp(name, p, name_len)) { // Point p to variable value p += name_len + 1; // Point s to the end of the value s = (const char *) memchr(p, '&', (size_t)(e - p)); if (s == NULL) { s = e; } assert(s >= p); // Decode variable into destination buffer if ((size_t) (s - p) < dst_len) { len = url_decode(p, (size_t)(s - p), dst, dst_len, 1); } break; } } return len; } int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name, char *dst, size_t dst_size) { const char *s, *p, *end; int name_len, len = -1; dst[0] = '\0'; if ((s = mg_get_header(conn, "Cookie")) == NULL) { return 0; } name_len = strlen(cookie_name); end = s + strlen(s); for (; (s = strstr(s, cookie_name)) != NULL; s += name_len) if (s[name_len] == '=') { s += name_len + 1; if ((p = strchr(s, ' ')) == NULL) p = end; if (p[-1] == ';') p--; if (*s == '"' && p[-1] == '"' && p > s + 1) { s++; p--; } if ((size_t) (p - s) < dst_size) { len = (p - s) + 1; mg_strlcpy(dst, s, (size_t)len); } break; } return len; } // Mongoose allows to specify multiple directories to serve, // like /var/www,/~bob=/home/bob. That means that root directory depends on URI. // This function returns root dir for given URI. static int get_document_root(const struct mg_connection *conn, struct vec *document_root) { const char *root, *uri; int len_of_matched_uri; struct vec uri_vec, path_vec; uri = conn->request_info.uri; len_of_matched_uri = 0; root = next_option(conn->ctx->config[DOCUMENT_ROOT], document_root, NULL); while ((root = next_option(root, &uri_vec, &path_vec)) != NULL) { if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) { *document_root = path_vec; len_of_matched_uri = uri_vec.len; break; } } return len_of_matched_uri; } static void convert_uri_to_file_name(struct mg_connection *conn, const char *uri, char *buf, size_t buf_len) { struct vec vec = {0}; int match_len; match_len = get_document_root(conn, &vec); mg_snprintf(conn, buf, buf_len, "%.*s%s", (int) vec.len, vec.ptr, uri + match_len); #if defined(_WIN32) && !defined(__SYMBIAN32__) change_slashes_to_backslashes(buf); #endif // _WIN32 DEBUG_TRACE(("[%s] -> [%s], [%.*s]", uri, buf, (int) vec.len, vec.ptr)); } static int sslize(struct mg_connection *conn, int (*func)(SSL *)) { return (conn->ssl = SSL_new(conn->ctx->ssl_ctx)) != NULL && SSL_set_fd(conn->ssl, conn->client.sock) == 1 && func(conn->ssl) == 1; } static struct mg_connection *mg_connect(struct mg_connection *conn, const char *host, int port, int use_ssl) { struct mg_connection *newconn = NULL; struct sockaddr_in sin; struct hostent *he; int sock; if (conn->ctx->ssl_ctx == NULL && use_ssl) { cry(conn, "%s: SSL is not initialized", __func__); } else if ((he = gethostbyname(host)) == NULL) { cry(conn, "%s: gethostbyname(%s): %s", __func__, host, strerror(ERRNO)); } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { cry(conn, "%s: socket: %s", __func__, strerror(ERRNO)); } else { sin.sin_family = AF_INET; sin.sin_port = htons((uint16_t) port); sin.sin_addr = * (struct in_addr *) he->h_addr_list[0]; if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) { cry(conn, "%s: connect(%s:%d): %s", __func__, host, port, strerror(ERRNO)); closesocket(sock); } else if ((newconn = (struct mg_connection *) calloc(1, sizeof(*newconn))) == NULL) { cry(conn, "%s: calloc: %s", __func__, strerror(ERRNO)); closesocket(sock); } else { newconn->client.sock = sock; newconn->client.rsa.u.sin = sin; if (use_ssl) { sslize(newconn, SSL_connect); } } } return newconn; } // Check whether full request is buffered. Return: // -1 if request is malformed // 0 if request is not yet fully buffered // >0 actual request length, including last \r\n\r\n static int get_request_len(const char *buf, int buflen) { const char *s, *e; int len = 0; DEBUG_TRACE(("buf: %p, len: %d", buf, buflen)); for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++) // Control characters are not allowed but >=128 is. if (!isprint(* (const unsigned char *) s) && *s != '\r' && *s != '\n' && * (const unsigned char *) s < 128) { len = -1; } else if (s[0] == '\n' && s[1] == '\n') { len = (int) (s - buf) + 2; } else if (s[0] == '\n' && &s[1] < e && s[1] == '\r' && s[2] == '\n') { len = (int) (s - buf) + 3; } return len; } // Convert month to the month number. Return -1 on error, or month number static int get_month_index(const char *s) { size_t i; for (i = 0; i < ARRAY_SIZE(month_names); i++) if (!strcmp(s, month_names[i])) return (int) i; return -1; } // Parse UTC date-time string, and return the corresponding time_t value. static time_t parse_date_string(const char *datetime) { static const unsigned short days_before_month[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; char month_str[32]; int second, minute, hour, day, month, year, leap_days, days; time_t result = (time_t) 0; if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d", &day, month_str, &year, &hour, &minute, &second) == 6) || (sscanf(datetime, "%d %3s %d %d:%d:%d", &day, month_str, &year, &hour, &minute, &second) == 6) || (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d", &day, month_str, &year, &hour, &minute, &second) == 6) || (sscanf(datetime, "%d-%3s-%d %d:%d:%d", &day, month_str, &year, &hour, &minute, &second) == 6)) && year > 1970 && (month = get_month_index(month_str)) != -1) { year -= 1970; leap_days = year / 4 - year / 100 + year / 400; days = year * 365 + days_before_month[month] + (day - 1) + leap_days; result = days * 24 * 3600 + hour * 3600 + minute * 60 + second; } return result; } // Protect against directory disclosure attack by removing '..', // excessive '/' and '\' characters static void remove_double_dots_and_double_slashes(char *s) { char *p = s; while (*s != '\0') { *p++ = *s++; if (s[-1] == '/' || s[-1] == '\\') { // Skip all following slashes and backslashes while (*s == '/' || *s == '\\') { s++; } // Skip all double-dots while (*s == '.' && s[1] == '.') { s += 2; } } } *p = '\0'; } static const struct { const char *extension; size_t ext_len; const char *mime_type; size_t mime_type_len; } builtin_mime_types[] = { {".html", 5, "text/html", 9}, {".htm", 4, "text/html", 9}, {".shtm", 5, "text/html", 9}, {".shtml", 6, "text/html", 9}, {".css", 4, "text/css", 8}, {".js", 3, "application/x-javascript", 24}, {".ico", 4, "image/x-icon", 12}, {".gif", 4, "image/gif", 9}, {".jpg", 4, "image/jpeg", 10}, {".jpeg", 5, "image/jpeg", 10}, {".png", 4, "image/png", 9}, {".svg", 4, "image/svg+xml", 13}, {".torrent", 8, "application/x-bittorrent", 24}, {".wav", 4, "audio/x-wav", 11}, {".mp3", 4, "audio/x-mp3", 11}, {".mid", 4, "audio/mid", 9}, {".m3u", 4, "audio/x-mpegurl", 15}, {".ram", 4, "audio/x-pn-realaudio", 20}, {".xml", 4, "text/xml", 8}, {".xslt", 5, "application/xml", 15}, {".ra", 3, "audio/x-pn-realaudio", 20}, {".doc", 4, "application/msword", 19}, {".exe", 4, "application/octet-stream", 24}, {".zip", 4, "application/x-zip-compressed", 28}, {".xls", 4, "application/excel", 17}, {".tgz", 4, "application/x-tar-gz", 20}, {".tar", 4, "application/x-tar", 17}, {".gz", 3, "application/x-gunzip", 20}, {".arj", 4, "application/x-arj-compressed", 28}, {".rar", 4, "application/x-arj-compressed", 28}, {".rtf", 4, "application/rtf", 15}, {".pdf", 4, "application/pdf", 15}, {".swf", 4, "application/x-shockwave-flash",29}, {".mpg", 4, "video/mpeg", 10}, {".mpeg", 5, "video/mpeg", 10}, {".mp4", 4, "video/mp4", 9}, {".m4v", 4, "video/x-m4v", 11}, {".asf", 4, "video/x-ms-asf", 14}, {".avi", 4, "video/x-msvideo", 15}, {".bmp", 4, "image/bmp", 9}, {NULL, 0, NULL, 0} }; // Look at the "path" extension and figure what mime type it has. // Store mime type in the vector. static void get_mime_type(struct mg_context *ctx, const char *path, struct vec *vec) { struct vec ext_vec, mime_vec; const char *list, *ext; size_t i, path_len; path_len = strlen(path); // Scan user-defined mime types first, in case user wants to // override default mime types. list = ctx->config[EXTRA_MIME_TYPES]; while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { // ext now points to the path suffix ext = path + path_len - ext_vec.len; if (mg_strncasecmp(ext, ext_vec.ptr, ext_vec.len) == 0) { *vec = mime_vec; return; } } // Now scan built-in mime types for (i = 0; builtin_mime_types[i].extension != NULL; i++) { ext = path + (path_len - builtin_mime_types[i].ext_len); if (path_len > builtin_mime_types[i].ext_len && mg_strcasecmp(ext, builtin_mime_types[i].extension) == 0) { vec->ptr = builtin_mime_types[i].mime_type; vec->len = builtin_mime_types[i].mime_type_len; return; } } // Nothing found. Fall back to "text/plain" vec->ptr = "text/plain"; vec->len = 10; } #ifndef HAVE_MD5 typedef struct MD5Context { uint32_t buf[4]; uint32_t bits[2]; unsigned char in[64]; } MD5_CTX; #if defined(__BYTE_ORDER) && (__BYTE_ORDER == 1234) #define byteReverse(buf, len) // Do nothing #else static void byteReverse(unsigned char *buf, unsigned longs) { uint32_t t; do { t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 | ((unsigned) buf[1] << 8 | buf[0]); *(uint32_t *) buf = t; buf += 4; } while (--longs); } #endif #define F1(x, y, z) (z ^ (x & (y ^ z))) #define F2(x, y, z) F1(z, x, y) #define F3(x, y, z) (x ^ y ^ z) #define F4(x, y, z) (y ^ (x | ~z)) #define MD5STEP(f, w, x, y, z, data, s) \ ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) // Start MD5 accumulation. Set bit count to 0 and buffer to mysterious // initialization constants. static void MD5Init(MD5_CTX *ctx) { ctx->buf[0] = 0x67452301; ctx->buf[1] = 0xefcdab89; ctx->buf[2] = 0x98badcfe; ctx->buf[3] = 0x10325476; ctx->bits[0] = 0; ctx->bits[1] = 0; } static void MD5Transform(uint32_t buf[4], uint32_t const in[16]) { uint32_t a, b, c, d; a = buf[0]; b = buf[1]; c = buf[2]; d = buf[3]; MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); buf[0] += a; buf[1] += b; buf[2] += c; buf[3] += d; } static void MD5Update(MD5_CTX *ctx, unsigned char const *buf, unsigned len) { uint32_t t; t = ctx->bits[0]; if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) ctx->bits[1]++; ctx->bits[1] += len >> 29; t = (t >> 3) & 0x3f; if (t) { unsigned char *p = (unsigned char *) ctx->in + t; t = 64 - t; if (len < t) { memcpy(p, buf, len); return; } memcpy(p, buf, t); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (uint32_t *) ctx->in); buf += t; len -= t; } while (len >= 64) { memcpy(ctx->in, buf, 64); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (uint32_t *) ctx->in); buf += 64; len -= 64; } memcpy(ctx->in, buf, len); } static void MD5Final(unsigned char digest[16], MD5_CTX *ctx) { unsigned count; unsigned char *p; count = (ctx->bits[0] >> 3) & 0x3F; p = ctx->in + count; *p++ = 0x80; count = 64 - 1 - count; if (count < 8) { memset(p, 0, count); byteReverse(ctx->in, 16); MD5Transform(ctx->buf, (uint32_t *) ctx->in); memset(ctx->in, 0, 56); } else { memset(p, 0, count - 8); } byteReverse(ctx->in, 14); memcpy(ctx->in + 14 * sizeof(uint32_t), ctx->bits, sizeof(ctx->bits)); MD5Transform(ctx->buf, (uint32_t *) ctx->in); byteReverse((unsigned char *) ctx->buf, 4); memcpy(digest, ctx->buf, 16); memset((char *) ctx, 0, sizeof(*ctx)); } #endif // !HAVE_MD5 // Stringify binary data. Output buffer must be twice as big as input, // because each byte takes 2 bytes in string representation static void bin2str(char *to, const unsigned char *p, size_t len) { static const char *hex = "0123456789abcdef"; for (; len--; p++) { *to++ = hex[p[0] >> 4]; *to++ = hex[p[0] & 0x0f]; } *to = '\0'; } // Return stringified MD5 hash for list of vectors. Buffer must be 33 bytes. void mg_md5(char *buf, ...) { unsigned char hash[16]; const char *p; va_list ap; MD5_CTX ctx; MD5Init(&ctx); va_start(ap, buf); while ((p = va_arg(ap, const char *)) != NULL) { MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p)); } va_end(ap); MD5Final(hash, &ctx); bin2str(buf, hash, sizeof(hash)); } // Check the user's password, return 1 if OK static int check_password(const char *method, const char *ha1, const char *uri, const char *nonce, const char *nc, const char *cnonce, const char *qop, const char *response) { char ha2[32 + 1], expected_response[32 + 1]; // Some of the parameters may be NULL if (method == NULL || nonce == NULL || nc == NULL || cnonce == NULL || qop == NULL || response == NULL) { return 0; } // NOTE(lsm): due to a bug in MSIE, we do not compare the URI // TODO(lsm): check for authentication timeout if (// strcmp(dig->uri, c->ouri) != 0 || strlen(response) != 32 // || now - strtoul(dig->nonce, NULL, 10) > 3600 ) { return 0; } mg_md5(ha2, method, ":", uri, NULL); mg_md5(expected_response, ha1, ":", nonce, ":", nc, ":", cnonce, ":", qop, ":", ha2, NULL); return mg_strcasecmp(response, expected_response) == 0; } // Use the global passwords file, if specified by auth_gpass option, // or search for .htpasswd in the requested directory. static FILE *open_auth_file(struct mg_connection *conn, const char *path) { struct mg_context *ctx = conn->ctx; char name[PATH_MAX]; const char *p, *e; struct mgstat st; FILE *fp; if (ctx->config[GLOBAL_PASSWORDS_FILE] != NULL) { // Use global passwords file fp = mg_fopen(ctx->config[GLOBAL_PASSWORDS_FILE], "r"); if (fp == NULL) cry(fc(ctx), "fopen(%s): %s", ctx->config[GLOBAL_PASSWORDS_FILE], strerror(ERRNO)); } else if (!mg_stat(path, &st) && st.is_directory) { (void) mg_snprintf(conn, name, sizeof(name), "%s%c%s", path, DIRSEP, PASSWORDS_FILE_NAME); fp = mg_fopen(name, "r"); } else { // Try to find .htpasswd in requested directory. for (p = path, e = p + strlen(p) - 1; e > p; e--) if (IS_DIRSEP_CHAR(*e)) break; (void) mg_snprintf(conn, name, sizeof(name), "%.*s%c%s", (int) (e - p), p, DIRSEP, PASSWORDS_FILE_NAME); fp = mg_fopen(name, "r"); } return fp; } // Parsed Authorization header struct ah { char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; }; static int parse_auth_header(struct mg_connection *conn, char *buf, size_t buf_size, struct ah *ah) { char *name, *value, *s; const char *auth_header; if ((auth_header = mg_get_header(conn, "Authorization")) == NULL || mg_strncasecmp(auth_header, "Digest ", 7) != 0) { return 0; } // Make modifiable copy of the auth header (void) mg_strlcpy(buf, auth_header + 7, buf_size); s = buf; (void) memset(ah, 0, sizeof(*ah)); // Parse authorization header for (;;) { // Gobble initial spaces while (isspace(* (unsigned char *) s)) { s++; } name = skip_quoted(&s, "=", " ", 0); // Value is either quote-delimited, or ends at first comma or space. if (s[0] == '\"') { s++; value = skip_quoted(&s, "\"", " ", '\\'); if (s[0] == ',') { s++; } } else { value = skip_quoted(&s, ", ", " ", 0); // IE uses commas, FF uses spaces } if (*name == '\0') { break; } if (!strcmp(name, "username")) { ah->user = value; } else if (!strcmp(name, "cnonce")) { ah->cnonce = value; } else if (!strcmp(name, "response")) { ah->response = value; } else if (!strcmp(name, "uri")) { ah->uri = value; } else if (!strcmp(name, "qop")) { ah->qop = value; } else if (!strcmp(name, "nc")) { ah->nc = value; } else if (!strcmp(name, "nonce")) { ah->nonce = value; } } // CGI needs it as REMOTE_USER if (ah->user != NULL) { conn->request_info.remote_user = mg_strdup(ah->user); } else { return 0; } return 1; } // Authorize against the opened passwords file. Return 1 if authorized. static int authorize(struct mg_connection *conn, FILE *fp) { struct ah ah; char line[256], f_user[256], ha1[256], f_domain[256], buf[BUFSIZ]; if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) { return 0; } // Loop over passwords file while (fgets(line, sizeof(line), fp) != NULL) { if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) != 3) { continue; } if (!strcmp(ah.user, f_user) && !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain)) return check_password( conn->request_info.request_method, ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop, ah.response); } return 0; } // Return 1 if request is authorised, 0 otherwise. static int check_authorization(struct mg_connection *conn, const char *path) { FILE *fp; char fname[PATH_MAX]; struct vec uri_vec, filename_vec; const char *list; int authorized; fp = NULL; authorized = 1; list = conn->ctx->config[PROTECT_URI]; while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { (void) mg_snprintf(conn, fname, sizeof(fname), "%.*s", (int) filename_vec.len, filename_vec.ptr); if ((fp = mg_fopen(fname, "r")) == NULL) { cry(conn, "%s: cannot open %s: %s", __func__, fname, strerror(errno)); } break; } } if (fp == NULL) { fp = open_auth_file(conn, path); } if (fp != NULL) { authorized = authorize(conn, fp); (void) fclose(fp); } return authorized; } static void send_authorization_request(struct mg_connection *conn) { conn->request_info.status_code = 401; (void) mg_printf(conn, "HTTP/1.1 401 Unauthorized\r\n" "Content-Length: 0\r\n" "WWW-Authenticate: Digest qop=\"auth\", " "realm=\"%s\", nonce=\"%lu\"\r\n\r\n", conn->ctx->config[AUTHENTICATION_DOMAIN], (unsigned long) time(NULL)); } static int is_authorized_for_put(struct mg_connection *conn) { FILE *fp; int ret = 0; fp = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ? NULL : mg_fopen(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE], "r"); if (fp != NULL) { ret = authorize(conn, fp); (void) fclose(fp); } return ret; } int mg_modify_passwords_file(const char *fname, const char *domain, const char *user, const char *pass) { int found; char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX]; FILE *fp, *fp2; found = 0; fp = fp2 = NULL; // Regard empty password as no password - remove user record. if (pass != NULL && pass[0] == '\0') { pass = NULL; } (void) snprintf(tmp, sizeof(tmp), "%s.tmp", fname); // Create the file if does not exist if ((fp = mg_fopen(fname, "a+")) != NULL) { (void) fclose(fp); } // Open the given file and temporary file if ((fp = mg_fopen(fname, "r")) == NULL) { return 0; } else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) { fclose(fp); return 0; } // Copy the stuff to temporary file while (fgets(line, sizeof(line), fp) != NULL) { if (sscanf(line, "%[^:]:%[^:]:%*s", u, d) != 2) { continue; } if (!strcmp(u, user) && !strcmp(d, domain)) { found++; if (pass != NULL) { mg_md5(ha1, user, ":", domain, ":", pass, NULL); fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); } } else { (void) fprintf(fp2, "%s", line); } } // If new user, just add it if (!found && pass != NULL) { mg_md5(ha1, user, ":", domain, ":", pass, NULL); (void) fprintf(fp2, "%s:%s:%s\n", user, domain, ha1); } // Close files (void) fclose(fp); (void) fclose(fp2); // Put the temp file in place of real file (void) mg_remove(fname); (void) mg_rename(tmp, fname); return 1; } struct de { struct mg_connection *conn; char *file_name; struct mgstat st; }; static void url_encode(const char *src, char *dst, size_t dst_len) { static const char *dont_escape = "._-$,;~()"; static const char *hex = "0123456789abcdef"; const char *end = dst + dst_len - 1; for (; *src != '\0' && dst < end; src++, dst++) { if (isalnum(*(const unsigned char *) src) || strchr(dont_escape, * (const unsigned char *) src) != NULL) { *dst = *src; } else if (dst + 2 < end) { dst[0] = '%'; dst[1] = hex[(* (const unsigned char *) src) >> 4]; dst[2] = hex[(* (const unsigned char *) src) & 0xf]; dst += 2; } } *dst = '\0'; } static void print_dir_entry(struct de *de) { char size[64], mod[64], href[PATH_MAX]; if (de->st.is_directory) { (void) mg_snprintf(de->conn, size, sizeof(size), "%s", "[DIRECTORY]"); } else { // We use (signed) cast below because MSVC 6 compiler cannot // convert unsigned __int64 to double. Sigh. if (de->st.size < 1024) { (void) mg_snprintf(de->conn, size, sizeof(size), "%lu", (unsigned long) de->st.size); } else if (de->st.size < 1024 * 1024) { (void) mg_snprintf(de->conn, size, sizeof(size), "%.1fk", (double) de->st.size / 1024.0); } else if (de->st.size < 1024 * 1024 * 1024) { (void) mg_snprintf(de->conn, size, sizeof(size), "%.1fM", (double) de->st.size / 1048576); } else { (void) mg_snprintf(de->conn, size, sizeof(size), "%.1fG", (double) de->st.size / 1073741824); } } (void) strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&de->st.mtime)); url_encode(de->file_name, href, sizeof(href)); de->conn->num_bytes_sent += mg_printf(de->conn, "%s%s" " %s  %s\n", de->conn->request_info.uri, href, de->st.is_directory ? "/" : "", de->file_name, de->st.is_directory ? "/" : "", mod, size); } // This function is called from send_directory() and used for // sorting directory entries by size, or name, or modification time. // On windows, __cdecl specification is needed in case if project is built // with __stdcall convention. qsort always requires __cdels callback. static int WINCDECL compare_dir_entries(const void *p1, const void *p2) { const struct de *a = (const struct de *) p1, *b = (const struct de *) p2; const char *query_string = a->conn->request_info.query_string; int cmp_result = 0; if (query_string == NULL) { query_string = "na"; } if (a->st.is_directory && !b->st.is_directory) { return -1; // Always put directories on top } else if (!a->st.is_directory && b->st.is_directory) { return 1; // Always put directories on top } else if (*query_string == 'n') { cmp_result = strcmp(a->file_name, b->file_name); } else if (*query_string == 's') { cmp_result = a->st.size == b->st.size ? 0 : a->st.size > b->st.size ? 1 : -1; } else if (*query_string == 'd') { cmp_result = a->st.mtime == b->st.mtime ? 0 : a->st.mtime > b->st.mtime ? 1 : -1; } return query_string[1] == 'd' ? -cmp_result : cmp_result; } static int scan_directory(struct mg_connection *conn, const char *dir, void *data, void (*cb)(struct de *, void *)) { char path[PATH_MAX]; struct dirent *dp; DIR *dirp; struct de de; if ((dirp = opendir(dir)) == NULL) { return 0; } else { de.conn = conn; while ((dp = readdir(dirp)) != NULL) { // Do not show current dir and passwords file if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..") || !strcmp(dp->d_name, PASSWORDS_FILE_NAME)) continue; mg_snprintf(conn, path, sizeof(path), "%s%c%s", dir, DIRSEP, dp->d_name); // If we don't memset stat structure to zero, mtime will have // garbage and strftime() will segfault later on in // print_dir_entry(). memset is required only if mg_stat() // fails. For more details, see // http://code.google.com/p/mongoose/issues/detail?id=79 if (mg_stat(path, &de.st) != 0) { memset(&de.st, 0, sizeof(de.st)); } de.file_name = dp->d_name; cb(&de, data); } (void) closedir(dirp); } return 1; } struct dir_scan_data { struct de *entries; int num_entries; int arr_size; }; static void dir_scan_callback(struct de *de, void *data) { struct dir_scan_data *dsd = (struct dir_scan_data *) data; if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) { dsd->arr_size *= 2; dsd->entries = (struct de *) realloc(dsd->entries, dsd->arr_size * sizeof(dsd->entries[0])); } if (dsd->entries == NULL) { // TODO(lsm): propagate an error to the caller dsd->num_entries = 0; } else { dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name); dsd->entries[dsd->num_entries].st = de->st; dsd->entries[dsd->num_entries].conn = de->conn; dsd->num_entries++; } } static void handle_directory_request(struct mg_connection *conn, const char *dir) { int i, sort_direction; struct dir_scan_data data = { NULL, 0, 128 }; if (!scan_directory(conn, dir, &data, dir_scan_callback)) { send_http_error(conn, 500, "Cannot open directory", "Error: opendir(%s): %s", dir, strerror(ERRNO)); return; } sort_direction = conn->request_info.query_string != NULL && conn->request_info.query_string[1] == 'd' ? 'a' : 'd'; mg_printf(conn, "%s", "HTTP/1.1 200 OK\r\n" "Connection: close\r\n" "Content-Type: text/html; charset=utf-8\r\n\r\n"); conn->num_bytes_sent += mg_printf(conn, "Index of %s" "" "

Index of %s

"
       ""
       ""
       ""
       "",
       conn->request_info.uri, conn->request_info.uri,
       sort_direction, sort_direction, sort_direction);
 
   // Print first entry - link to a parent directory
   conn->num_bytes_sent += mg_printf(conn,
       ""
       "\n",
       conn->request_info.uri, "..", "Parent directory", "-", "-");
 
   // Sort and print directory entries
   qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
         compare_dir_entries);
   for (i = 0; i < data.num_entries; i++) {
     print_dir_entry(&data.entries[i]);
     free(data.entries[i].file_name);
   }
   free(data.entries);
 
   conn->num_bytes_sent += mg_printf(conn, "%s", "
NameModifiedSize

%s %s  %s
"); conn->request_info.status_code = 200; } // Send len bytes from the opened file to the client. static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t len) { char buf[BUFSIZ]; int to_read, num_read, num_written; while (len > 0) { // Calculate how much to read from the file in the buffer to_read = sizeof(buf); if ((int64_t) to_read > len) to_read = (int) len; // Read from file, exit the loop on error if ((num_read = fread(buf, 1, (size_t)to_read, fp)) == 0) break; // Send read bytes to the client, exit the loop on error if ((num_written = mg_write(conn, buf, (size_t)num_read)) != num_read) break; // Both read and were successful, adjust counters conn->num_bytes_sent += num_written; len -= num_written; } } static int parse_range_header(const char *header, int64_t *a, int64_t *b) { return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b); } static void gmt_time_string(char *buf, size_t buf_len, time_t *t) { strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t)); } static void handle_file_request(struct mg_connection *conn, const char *path, struct mgstat *stp) { char date[64], lm[64], etag[64], range[64]; const char *msg = "OK", *hdr; time_t curtime = time(NULL); int64_t cl, r1, r2; struct vec mime_vec; FILE *fp; int n; get_mime_type(conn->ctx, path, &mime_vec); cl = stp->size; conn->request_info.status_code = 200; range[0] = '\0'; if ((fp = mg_fopen(path, "rb")) == NULL) { send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path, strerror(ERRNO)); return; } set_close_on_exec(fileno(fp)); // If Range: header specified, act accordingly r1 = r2 = 0; hdr = mg_get_header(conn, "Range"); if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0) { conn->request_info.status_code = 206; (void) fseeko(fp, (off_t) r1, SEEK_SET); cl = n == 2 ? r2 - r1 + 1: cl - r1; (void) mg_snprintf(conn, range, sizeof(range), "Content-Range: bytes " "%" INT64_FMT "-%" INT64_FMT "/%" INT64_FMT "\r\n", r1, r1 + cl - 1, stp->size); msg = "Partial Content"; } // Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to // http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3 gmt_time_string(date, sizeof(date), &curtime); gmt_time_string(lm, sizeof(lm), &stp->mtime); (void) mg_snprintf(conn, etag, sizeof(etag), "%lx.%lx", (unsigned long) stp->mtime, (unsigned long) stp->size); (void) mg_printf(conn, "HTTP/1.1 %d %s\r\n" "Date: %s\r\n" "Last-Modified: %s\r\n" "Etag: \"%s\"\r\n" "Content-Type: %.*s\r\n" "Content-Length: %" INT64_FMT "\r\n" "Connection: %s\r\n" "Accept-Ranges: bytes\r\n" "%s\r\n", conn->request_info.status_code, msg, date, lm, etag, (int) mime_vec.len, mime_vec.ptr, cl, suggest_connection_header(conn), range); if (strcmp(conn->request_info.request_method, "HEAD") != 0) { send_file_data(conn, fp, cl); } (void) fclose(fp); } void mg_send_file(struct mg_connection *conn, const char *path) { struct mgstat st; if (mg_stat(path, &st) == 0) { handle_file_request(conn, path, &st); } else { send_http_error(conn, 404, "Not Found", "%s", "File not found"); } } // Parse HTTP headers from the given buffer, advance buffer to the point // where parsing stopped. static void parse_http_headers(char **buf, struct mg_request_info *ri) { int i; for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) { ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0); ri->http_headers[i].value = skip(buf, "\r\n"); if (ri->http_headers[i].name[0] == '\0') break; ri->num_headers = i + 1; } } static int is_valid_http_method(const char *method) { return !strcmp(method, "GET") || !strcmp(method, "POST") || !strcmp(method, "HEAD") || !strcmp(method, "CONNECT") || !strcmp(method, "PUT") || !strcmp(method, "DELETE") || !strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND"); } // Parse HTTP request, fill in mg_request_info structure. static int parse_http_request(char *buf, struct mg_request_info *ri) { int status = 0; // RFC says that all initial whitespaces should be ingored while (*buf != '\0' && isspace(* (unsigned char *) buf)) { buf++; } ri->request_method = skip(&buf, " "); ri->uri = skip(&buf, " "); ri->http_version = skip(&buf, "\r\n"); if (is_valid_http_method(ri->request_method) && strncmp(ri->http_version, "HTTP/", 5) == 0) { ri->http_version += 5; // Skip "HTTP/" parse_http_headers(&buf, ri); status = 1; } return status; } // Keep reading the input (either opened file descriptor fd, or socket sock, // or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the // buffer (which marks the end of HTTP request). Buffer buf may already // have some data. The length of the data is stored in nread. // Upon every read operation, increase nread by the number of bytes read. static int read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz, int *nread) { int n, request_len; request_len = 0; while (*nread < bufsiz && request_len == 0) { n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread); if (n <= 0) { break; } else { *nread += n; request_len = get_request_len(buf, *nread); } } return request_len; } // For given directory path, substitute it to valid index file. // Return 0 if index file has been found, -1 if not found. // If the file is found, it's stats is returned in stp. static int substitute_index_file(struct mg_connection *conn, char *path, size_t path_len, struct mgstat *stp) { const char *list = conn->ctx->config[INDEX_FILES]; struct mgstat st; struct vec filename_vec; size_t n = strlen(path); int found = 0; // The 'path' given to us points to the directory. Remove all trailing // directory separator characters from the end of the path, and // then append single directory separator character. while (n > 0 && IS_DIRSEP_CHAR(path[n - 1])) { n--; } path[n] = DIRSEP; // Traverse index files list. For each entry, append it to the given // path and see if the file exists. If it exists, break the loop while ((list = next_option(list, &filename_vec, NULL)) != NULL) { // Ignore too long entries that may overflow path buffer if (filename_vec.len > path_len - n) continue; // Prepare full path to the index file (void) mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1); // Does it exist? if (mg_stat(path, &st) == 0) { // Yes it does, break the loop *stp = st; found = 1; break; } } // If no index file exists, restore directory path if (!found) { path[n] = '\0'; } return found; } // Return True if we should reply 304 Not Modified. static int is_not_modified(const struct mg_connection *conn, const struct mgstat *stp) { const char *ims = mg_get_header(conn, "If-Modified-Since"); return ims != NULL && stp->mtime <= parse_date_string(ims); } static int forward_body_data(struct mg_connection *conn, FILE *fp, SOCKET sock, SSL *ssl) { const char *expect, *buffered; char buf[BUFSIZ]; int to_read, nread, buffered_len, success = 0; expect = mg_get_header(conn, "Expect"); assert(fp != NULL); if (conn->content_len == -1) { send_http_error(conn, 411, "Length Required", ""); } else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) { send_http_error(conn, 417, "Expectation Failed", ""); } else { if (expect != NULL) { (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n"); } buffered = conn->buf + conn->request_len; buffered_len = conn->data_len - conn->request_len; assert(buffered_len >= 0); assert(conn->consumed_content == 0); if (buffered_len > 0) { if ((int64_t) buffered_len > conn->content_len) { buffered_len = (int) conn->content_len; } push(fp, sock, ssl, buffered, (int64_t) buffered_len); conn->consumed_content += buffered_len; } while (conn->consumed_content < conn->content_len) { to_read = sizeof(buf); if ((int64_t) to_read > conn->content_len - conn->consumed_content) { to_read = (int) (conn->content_len - conn->consumed_content); } nread = pull(NULL, conn->client.sock, conn->ssl, buf, to_read); if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) { break; } conn->consumed_content += nread; } if (conn->consumed_content == conn->content_len) { success = 1; } // Each error code path in this function must send an error if (!success) { send_http_error(conn, 577, http_500_error, ""); } } return success; } #if !defined(NO_CGI) // This structure helps to create an environment for the spawned CGI program. // Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings, // last element must be NULL. // However, on Windows there is a requirement that all these VARIABLE=VALUE\0 // strings must reside in a contiguous buffer. The end of the buffer is // marked by two '\0' characters. // We satisfy both worlds: we create an envp array (which is vars), all // entries are actually pointers inside buf. struct cgi_env_block { struct mg_connection *conn; char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer int len; // Space taken char *vars[MAX_CGI_ENVIR_VARS]; // char **envp int nvars; // Number of variables }; // Append VARIABLE=VALUE\0 string to the buffer, and add a respective // pointer into the vars array. static char *addenv(struct cgi_env_block *block, const char *fmt, ...) { int n, space; char *added; va_list ap; // Calculate how much space is left in the buffer space = sizeof(block->buf) - block->len - 2; assert(space >= 0); // Make a pointer to the free space int the buffer added = block->buf + block->len; // Copy VARIABLE=VALUE\0 string into the free space va_start(ap, fmt); n = mg_vsnprintf(block->conn, added, (size_t) space, fmt, ap); va_end(ap); // Make sure we do not overflow buffer and the envp array if (n > 0 && n < space && block->nvars < (int) ARRAY_SIZE(block->vars) - 2) { // Append a pointer to the added string into the envp array block->vars[block->nvars++] = block->buf + block->len; // Bump up used length counter. Include \0 terminator block->len += n + 1; } return added; } static void prepare_cgi_environment(struct mg_connection *conn, const char *prog, struct cgi_env_block *blk) { const char *s, *slash; struct vec var_vec, root; char *p; int i; blk->len = blk->nvars = 0; blk->conn = conn; get_document_root(conn, &root); addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]); addenv(blk, "SERVER_ROOT=%.*s", root.len, root.ptr); addenv(blk, "DOCUMENT_ROOT=%.*s", root.len, root.ptr); // Prepare the environment block addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.u.sin.sin_port)); addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method); addenv(blk, "REMOTE_ADDR=%s", inet_ntoa(conn->client.rsa.u.sin.sin_addr)); addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port); addenv(blk, "REQUEST_URI=%s", conn->request_info.uri); // SCRIPT_NAME assert(conn->request_info.uri[0] == '/'); slash = strrchr(conn->request_info.uri, '/'); if ((s = strrchr(prog, '/')) == NULL) s = prog; addenv(blk, "SCRIPT_NAME=%.*s%s", slash - conn->request_info.uri, conn->request_info.uri, s); addenv(blk, "SCRIPT_FILENAME=%s", prog); addenv(blk, "PATH_TRANSLATED=%s", prog); addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on"); if ((s = mg_get_header(conn, "Content-Type")) != NULL) addenv(blk, "CONTENT_TYPE=%s", s); if (conn->request_info.query_string != NULL) addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string); if ((s = mg_get_header(conn, "Content-Length")) != NULL) addenv(blk, "CONTENT_LENGTH=%s", s); if ((s = getenv("PATH")) != NULL) addenv(blk, "PATH=%s", s); #if defined(_WIN32) if ((s = getenv("COMSPEC")) != NULL) addenv(blk, "COMSPEC=%s", s); if ((s = getenv("SYSTEMROOT")) != NULL) addenv(blk, "SYSTEMROOT=%s", s); #else if ((s = getenv("LD_LIBRARY_PATH")) != NULL) addenv(blk, "LD_LIBRARY_PATH=%s", s); #endif // _WIN32 if ((s = getenv("PERLLIB")) != NULL) addenv(blk, "PERLLIB=%s", s); if (conn->request_info.remote_user != NULL) { addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user); addenv(blk, "%s", "AUTH_TYPE=Digest"); } // Add all headers as HTTP_* variables for (i = 0; i < conn->request_info.num_headers; i++) { p = addenv(blk, "HTTP_%s=%s", conn->request_info.http_headers[i].name, conn->request_info.http_headers[i].value); // Convert variable name into uppercase, and change - to _ for (; *p != '=' && *p != '\0'; p++) { if (*p == '-') *p = '_'; *p = (char) toupper(* (unsigned char *) p); } } // Add user-specified variables s = conn->ctx->config[CGI_ENVIRONMENT]; while ((s = next_option(s, &var_vec, NULL)) != NULL) { addenv(blk, "%.*s", var_vec.len, var_vec.ptr); } blk->vars[blk->nvars++] = NULL; blk->buf[blk->len++] = '\0'; assert(blk->nvars < (int) ARRAY_SIZE(blk->vars)); assert(blk->len > 0); assert(blk->len < (int) sizeof(blk->buf)); } static void handle_cgi_request(struct mg_connection *conn, const char *prog) { int headers_len, data_len, i, fd_stdin[2], fd_stdout[2]; const char *status; char buf[BUFSIZ], *pbuf, dir[PATH_MAX], *p; struct mg_request_info ri; struct cgi_env_block blk; FILE *in, *out; pid_t pid; prepare_cgi_environment(conn, prog, &blk); // CGI must be executed in its own directory. 'dir' must point to the // directory containing executable program, 'p' must point to the // executable program name relative to 'dir'. (void) mg_snprintf(conn, dir, sizeof(dir), "%s", prog); if ((p = strrchr(dir, DIRSEP)) != NULL) { *p++ = '\0'; } else { dir[0] = '.', dir[1] = '\0'; p = (char *) prog; } pid = (pid_t) -1; fd_stdin[0] = fd_stdin[1] = fd_stdout[0] = fd_stdout[1] = -1; in = out = NULL; if (pipe(fd_stdin) != 0 || pipe(fd_stdout) != 0) { send_http_error(conn, 500, http_500_error, "Cannot create CGI pipe: %s", strerror(ERRNO)); goto done; } else if ((pid = spawn_process(conn, p, blk.buf, blk.vars, fd_stdin[0], fd_stdout[1], dir)) == (pid_t) -1) { goto done; } else if ((in = fdopen(fd_stdin[1], "wb")) == NULL || (out = fdopen(fd_stdout[0], "rb")) == NULL) { send_http_error(conn, 500, http_500_error, "fopen: %s", strerror(ERRNO)); goto done; } setbuf(in, NULL); setbuf(out, NULL); // spawn_process() must close those! // If we don't mark them as closed, close() attempt before // return from this function throws an exception on Windows. // Windows does not like when closed descriptor is closed again. fd_stdin[0] = fd_stdout[1] = -1; // Send POST data to the CGI process if needed if (!strcmp(conn->request_info.request_method, "POST") && !forward_body_data(conn, in, INVALID_SOCKET, NULL)) { goto done; } // Now read CGI reply into a buffer. We need to set correct // status code, thus we need to see all HTTP headers first. // Do not send anything back to client, until we buffer in all // HTTP headers. data_len = 0; headers_len = read_request(out, INVALID_SOCKET, NULL, buf, sizeof(buf), &data_len); if (headers_len <= 0) { send_http_error(conn, 500, http_500_error, "CGI program sent malformed HTTP headers: [%.*s]", data_len, buf); goto done; } pbuf = buf; buf[headers_len - 1] = '\0'; parse_http_headers(&pbuf, &ri); // Make up and send the status line status = get_header(&ri, "Status"); conn->request_info.status_code = status == NULL ? 200 : atoi(status); (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n", conn->request_info.status_code); // Send headers for (i = 0; i < ri.num_headers; i++) { mg_printf(conn, "%s: %s\r\n", ri.http_headers[i].name, ri.http_headers[i].value); } (void) mg_write(conn, "\r\n", 2); // Send chunk of data that may be read after the headers conn->num_bytes_sent += mg_write(conn, buf + headers_len, (size_t)(data_len - headers_len)); // Read the rest of CGI output and send to the client send_file_data(conn, out, INT64_MAX); done: if (pid != (pid_t) -1) { kill(pid, SIGKILL); } if (fd_stdin[0] != -1) { (void) close(fd_stdin[0]); } if (fd_stdout[1] != -1) { (void) close(fd_stdout[1]); } if (in != NULL) { (void) fclose(in); } else if (fd_stdin[1] != -1) { (void) close(fd_stdin[1]); } if (out != NULL) { (void) fclose(out); } else if (fd_stdout[0] != -1) { (void) close(fd_stdout[0]); } } #endif // !NO_CGI // For a given PUT path, create all intermediate subdirectories // for given path. Return 0 if the path itself is a directory, // or -1 on error, 1 if OK. static int put_dir(const char *path) { char buf[PATH_MAX]; const char *s, *p; struct mgstat st; int len, res = 1; for (s = p = path + 2; (p = strchr(s, DIRSEP)) != NULL; s = ++p) { len = p - path; if (len >= (int) sizeof(buf)) { res = -1; break; } memcpy(buf, path, len); buf[len] = '\0'; // Try to create intermediate directory DEBUG_TRACE(("mkdir(%s)", buf)); if (mg_stat(buf, &st) == -1 && mg_mkdir(buf, 0755) != 0) { res = -1; break; } // Is path itself a directory? if (p[1] == '\0') { res = 0; } } return res; } static void put_file(struct mg_connection *conn, const char *path) { struct mgstat st; const char *range; int64_t r1, r2; FILE *fp; int rc; conn->request_info.status_code = mg_stat(path, &st) == 0 ? 200 : 201; if ((rc = put_dir(path)) == 0) { mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->request_info.status_code); } else if (rc == -1) { send_http_error(conn, 500, http_500_error, "put_dir(%s): %s", path, strerror(ERRNO)); } else if ((fp = mg_fopen(path, "wb+")) == NULL) { send_http_error(conn, 500, http_500_error, "fopen(%s): %s", path, strerror(ERRNO)); } else { set_close_on_exec(fileno(fp)); range = mg_get_header(conn, "Content-Range"); r1 = r2 = 0; if (range != NULL && parse_range_header(range, &r1, &r2) > 0) { conn->request_info.status_code = 206; // TODO(lsm): handle seek error (void) fseeko(fp, (off_t) r1, SEEK_SET); } if (forward_body_data(conn, fp, INVALID_SOCKET, NULL)) (void) mg_printf(conn, "HTTP/1.1 %d OK\r\n\r\n", conn->request_info.status_code); (void) fclose(fp); } } static void send_ssi_file(struct mg_connection *, const char *, FILE *, int); static void do_ssi_include(struct mg_connection *conn, const char *ssi, char *tag, int include_level) { char file_name[BUFSIZ], path[PATH_MAX], *p; struct vec root = {0}; int is_ssi; FILE *fp; get_document_root(conn, &root); // sscanf() is safe here, since send_ssi_file() also uses buffer // of size BUFSIZ to get the tag. So strlen(tag) is always < BUFSIZ. if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) { // File name is relative to the webserver root (void) mg_snprintf(conn, path, sizeof(path), "%.*s%c%s", (int) root.len, root.ptr, DIRSEP, file_name); } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1) { // File name is relative to the webserver working directory // or it is absolute system path (void) mg_snprintf(conn, path, sizeof(path), "%s", file_name); } else if (sscanf(tag, " \"%[^\"]\"", file_name) == 1) { // File name is relative to the currect document (void) mg_snprintf(conn, path, sizeof(path), "%s", ssi); if ((p = strrchr(path, DIRSEP)) != NULL) { p[1] = '\0'; } (void) mg_snprintf(conn, path + strlen(path), sizeof(path) - strlen(path), "%s", file_name); } else { cry(conn, "Bad SSI #include: [%s]", tag); return; } if ((fp = mg_fopen(path, "rb")) == NULL) { cry(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s", tag, path, strerror(ERRNO)); } else { set_close_on_exec(fileno(fp)); is_ssi = match_extension(path, conn->ctx->config[SSI_EXTENSIONS]); if (is_ssi) { send_ssi_file(conn, path, fp, include_level + 1); } else { send_file_data(conn, fp, INT64_MAX); } (void) fclose(fp); } } #if !defined(NO_POPEN) static void do_ssi_exec(struct mg_connection *conn, char *tag) { char cmd[BUFSIZ]; FILE *fp; if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) { cry(conn, "Bad SSI #exec: [%s]", tag); } else if ((fp = popen(cmd, "r")) == NULL) { cry(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(ERRNO)); } else { send_file_data(conn, fp, INT64_MAX); (void) pclose(fp); } } #endif // !NO_POPEN static void send_ssi_file(struct mg_connection *conn, const char *path, FILE *fp, int include_level) { char buf[BUFSIZ]; int ch, len, in_ssi_tag; if (include_level > 10) { cry(conn, "SSI #include level is too deep (%s)", path); return; } in_ssi_tag = 0; len = 0; while ((ch = fgetc(fp)) != EOF) { if (in_ssi_tag && ch == '>') { in_ssi_tag = 0; buf[len++] = (char) ch; buf[len] = '\0'; assert(len <= (int) sizeof(buf)); if (len < 6 || memcmp(buf, "