mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Vendor naett in-tree, de-amalgamated
naett has been a submodule pinned at v0.3.3; upstream has had no commits since April 2024, and we want to carry local changes (next up: a libcurl-backed HTTPS path for Linux). It's ~1500 lines of MIT C, smaller than several things we already vendor, so bring it in-tree and drop the submodule. Also drop the generated single-file amalgam (naett.c) that every build system was compiling, and build src/*.c directly instead - otherwise the file you edit isn't the file that gets compiled, which is a trap for anyone patching this. example/ and testrig/ (a whole Android Studio project) are gone with it. Two changes were needed to make the sources build on their own, both noted in ext/naett/README-ppsspp.md along with the upstream commit: - naett_internal.h now includes naett.h, which the amalgam pulled in first. - naett_linux.c now includes stdio.h/stdlib.h. It calls exit/calloc/realloc/ free/fprintf without ever including either, and only got away with it because naett_core.c sat above it in the concatenation. No functional change - Linux still has HTTPS_NOT_AVAILABLE set, so it doesn't build naett at all yet. Rename naett to naett-lib
This commit is contained in:
1 parent
56bba5f6f5
commit
2096df52f5
23 files changed
+1932
-27
No files matched your search
@@ -41,9 +41,6 @@
|
||||
[submodule "ext/rcheevos"]
|
||||
path = ext/rcheevos
|
||||
url = https://github.com/RetroAchievements/rcheevos.git
|
||||
[submodule "ext/naett"]
|
||||
path = ext/naett
|
||||
url = https://github.com/erkkah/naett.git
|
||||
[submodule "ext/libchdr"]
|
||||
path = ext/libchdr
|
||||
url = https://github.com/rtissera/libchdr.git
|
||||
|
||||
@@ -366,7 +366,8 @@
|
||||
<ClInclude Include="..\ext\lzma-sdk\Precomp.h" />
|
||||
<ClInclude Include="..\ext\lzma-sdk\RotateDefs.h" />
|
||||
<ClInclude Include="..\ext\minimp3\minimp3.h" />
|
||||
<ClInclude Include="..\ext\naett\naett.h" />
|
||||
<ClInclude Include="..\ext\naett-lib\naett.h" />
|
||||
<ClInclude Include="..\ext\naett-lib\src\naett_internal.h" />
|
||||
<ClInclude Include="..\ext\nanosvg\src\nanosvg.h" />
|
||||
<ClInclude Include="..\ext\nanosvg\src\nanosvgrast.h" />
|
||||
<ClInclude Include="..\ext\pugixml\pugiconfig.hpp" />
|
||||
@@ -868,7 +869,8 @@
|
||||
<ClCompile Include="..\ext\lzma-sdk\Lzma2Dec.c" />
|
||||
<ClCompile Include="..\ext\lzma-sdk\LzmaDec.c" />
|
||||
<ClCompile Include="..\ext\minimp3\minimp3.cpp" />
|
||||
<ClCompile Include="..\ext\naett\naett.c" />
|
||||
<ClCompile Include="..\ext\naett-lib\src\naett_core.c" />
|
||||
<ClCompile Include="..\ext\naett-lib\src\naett_win.c" />
|
||||
<ClCompile Include="..\ext\pugixml\pugixml.cpp" />
|
||||
<ClCompile Include="..\ext\vma\vk_mem_alloc.cpp" />
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
|
||||
@@ -500,8 +500,11 @@
|
||||
<ClInclude Include="Net\HTTPRequest.h">
|
||||
<Filter>Net</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ext\naett\naett.h">
|
||||
<Filter>ext\naett</Filter>
|
||||
<ClInclude Include="..\ext\naett-lib\src\naett_internal.h">
|
||||
<Filter>ext\naett-lib</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\ext\naett-lib\naett.h">
|
||||
<Filter>ext\naett-lib</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Net\HTTPNaettRequest.h">
|
||||
<Filter>Net</Filter>
|
||||
@@ -1231,8 +1234,11 @@
|
||||
<ClCompile Include="Net\HTTPRequest.cpp">
|
||||
<Filter>Net</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ext\naett\naett.c">
|
||||
<Filter>ext\naett</Filter>
|
||||
<ClCompile Include="..\ext\naett-lib\src\naett_core.c">
|
||||
<Filter>ext\naett-lib</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ext\naett-lib\src\naett_win.c">
|
||||
<Filter>ext\naett-lib</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Net\HTTPNaettRequest.cpp">
|
||||
<Filter>Net</Filter>
|
||||
@@ -1579,7 +1585,7 @@
|
||||
<Filter Include="ext\basis_universal">
|
||||
<UniqueIdentifier>{d6d5f6e0-1c72-496b-af11-6d52d5123033}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ext\naett">
|
||||
<Filter Include="ext\naett-lib">
|
||||
<UniqueIdentifier>{34f45db9-5c08-49cb-b349-b9e760ce3213}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ext\libchdr">
|
||||
@@ -1680,4 +1686,4 @@
|
||||
<Filter>Data\Collections</Filter>
|
||||
</Natvis>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Log.h"
|
||||
|
||||
#include "ext/naett/naett.h"
|
||||
#include "ext/naett-lib/naett.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
|
||||
#include "ext/naett/naett.h"
|
||||
#include "ext/naett-lib/naett.h"
|
||||
|
||||
namespace http {
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
#include "Common/Net/Resolve.h"
|
||||
|
||||
#ifndef HTTPS_NOT_AVAILABLE
|
||||
#include "ext/naett/naett.h"
|
||||
#include "ext/naett-lib/naett.h"
|
||||
#endif
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
|
||||
@@ -294,7 +294,8 @@
|
||||
<ClInclude Include="..\..\ext\lzma-sdk\RotateDefs.h" />
|
||||
<ClInclude Include="..\..\ext\minimp3\minimp3.h" />
|
||||
<ClInclude Include="..\..\ext\minimp3\minimp3_ex.h" />
|
||||
<ClInclude Include="..\..\ext\naett\naett.h" />
|
||||
<ClInclude Include="..\..\ext\naett-lib\naett.h" />
|
||||
<ClInclude Include="..\..\ext\naett-lib\src\naett_internal.h" />
|
||||
<ClInclude Include="..\..\ext\pugixml\pugiconfig.hpp" />
|
||||
<ClInclude Include="..\..\ext\pugixml\pugixml.hpp" />
|
||||
<ClInclude Include="targetver.h" />
|
||||
@@ -477,7 +478,8 @@
|
||||
<ClCompile Include="..\..\ext\lzma-sdk\Lzma2Dec.c" />
|
||||
<ClCompile Include="..\..\ext\lzma-sdk\LzmaDec.c" />
|
||||
<ClCompile Include="..\..\ext\minimp3\minimp3.cpp" />
|
||||
<ClCompile Include="..\..\ext\naett\naett.c" />
|
||||
<ClCompile Include="..\..\ext\naett-lib\src\naett_core.c" />
|
||||
<ClCompile Include="..\..\ext\naett-lib\src\naett_win.c" />
|
||||
<ClCompile Include="..\..\ext\pugixml\pugixml.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -505,4 +507,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -94,7 +94,7 @@
|
||||
<Filter Include="ext\basis_universal">
|
||||
<UniqueIdentifier>{8b92cc15-8e3e-45b1-ba80-ad39b1a9cdc8}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ext\naett">
|
||||
<Filter Include="ext\naett-lib">
|
||||
<UniqueIdentifier>{403c3154-5e8a-4da1-ba82-8f69bef25e14}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="ext\minimp3">
|
||||
@@ -467,8 +467,11 @@
|
||||
<ClCompile Include="..\..\Common\Net\HTTPNaettRequest.cpp">
|
||||
<Filter>Net</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\ext\naett\naett.c">
|
||||
<Filter>ext\naett</Filter>
|
||||
<ClCompile Include="..\..\ext\naett-lib\src\naett_core.c">
|
||||
<Filter>ext\naett-lib</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\ext\naett-lib\src\naett_win.c">
|
||||
<Filter>ext\naett-lib</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\ext\minimp3\minimp3.cpp">
|
||||
<Filter>ext\minimp3</Filter>
|
||||
@@ -997,8 +1000,11 @@
|
||||
<ClInclude Include="..\..\Common\Net\HTTPNaettRequest.h">
|
||||
<Filter>Net</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\ext\naett\naett.h">
|
||||
<Filter>ext\naett</Filter>
|
||||
<ClInclude Include="..\..\ext\naett-lib\src\naett_internal.h">
|
||||
<Filter>ext\naett-lib</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\ext\naett-lib\naett.h">
|
||||
<Filter>ext\naett-lib</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\Common\Math\CrossSIMD.h">
|
||||
<Filter>Math</Filter>
|
||||
@@ -1193,4 +1199,4 @@
|
||||
<Filter>ext\at3_standalone</Filter>
|
||||
</Text>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -165,7 +165,8 @@ SPIRV_CROSS_FILES := \
|
||||
$(SRC)/ext/SPIRV-Cross/spirv_cross_parsed_ir.cpp
|
||||
|
||||
NAETT_FILES := \
|
||||
${SRC}/ext/naett/naett.c
|
||||
${SRC}/ext/naett-lib/src/naett_core.c \
|
||||
${SRC}/ext/naett-lib/src/naett_android.c
|
||||
|
||||
MINIMP3_FILES := \
|
||||
${SRC}/ext/minimp3/minimp3.cpp
|
||||
|
||||
-1
Submodule ext/naett deleted from 5f695cfa9f.
@@ -1,12 +1,22 @@
|
||||
cmake_minimum_required (VERSION 3.10.0)
|
||||
project (naett)
|
||||
|
||||
set(SRC_DIR ../naett)
|
||||
set(SRC_DIR ../naett-lib/src)
|
||||
|
||||
set(ALL_SOURCE_FILES
|
||||
${SRC_DIR}/naett.c
|
||||
${SRC_DIR}/naett_core.c
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND ALL_SOURCE_FILES ${SRC_DIR}/naett_win.c)
|
||||
elseif(ANDROID)
|
||||
list(APPEND ALL_SOURCE_FILES ${SRC_DIR}/naett_android.c)
|
||||
elseif(APPLE)
|
||||
list(APPEND ALL_SOURCE_FILES ${SRC_DIR}/naett_osx.c)
|
||||
elseif(UNIX)
|
||||
list(APPEND ALL_SOURCE_FILES ${SRC_DIR}/naett_linux.c)
|
||||
endif()
|
||||
|
||||
add_library(naett STATIC ${ALL_SOURCE_FILES})
|
||||
if(WIN32)
|
||||
target_link_libraries(naett winhttp)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Erik Agsjö
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,23 @@
|
||||
# naett in PPSSPP
|
||||
|
||||
Vendored copy of [naett](https://github.com/erkkah/naett) by Erik Agsjö (MIT licensed, see `LICENSE`).
|
||||
|
||||
Upstream version: **v0.3.3** (`5f695cfa9fcbf30668a4d3ac4b4abf1cd89a1302`, 2024-04-13)
|
||||
|
||||
This used to be a git submodule. It's now in-tree because upstream has been dormant since
|
||||
April 2024 and we need to carry local changes (see below). It's ~1500 lines of C, which is
|
||||
smaller than several other things we already vendor.
|
||||
|
||||
## Local changes
|
||||
|
||||
Keep this list up to date - it's what makes it possible to move to a newer upstream later.
|
||||
|
||||
- Dropped the generated single-file amalgam (`naett.c`) and `src/amalgam.h`, along with
|
||||
`example/` and `testrig/`. We build `src/*.c` directly, so that the file you edit is the
|
||||
file that gets compiled.
|
||||
- `src/naett_internal.h`: added `#include "../naett.h"`. The amalgam included `naett.h`
|
||||
ahead of everything else; building the sources directly, each one needs it.
|
||||
- `src/naett_linux.c`: added `#include <stdio.h>` / `#include <stdlib.h>`. It uses
|
||||
`exit`/`calloc`/`realloc`/`free`/`fprintf` but never included either header - in the amalgam it
|
||||
got them from `naett_core.c` further up the concatenation. Building it on its own is an error
|
||||
with a modern compiler.
|
||||
@@ -0,0 +1,57 @@
|
||||
# naett /nɛt:/
|
||||
|
||||
Tiny HTTP client library in C.
|
||||
|
||||
Wraps native HTTP client functionality on macOS, Windows, Linux, iOS and Android in a single, simple non-blocking API.
|
||||
|
||||
## Using `naett`
|
||||
|
||||
Get the `naett.c` and `naett.h` files and throw them into your project. Check out the [example](./example) for a basic `Makefile` - based setup.
|
||||
|
||||
The library needs to be initialized by a call to `naettInit()`. On Android, you need to provide a `JavaVM*` handle in the call to `naettInit()`.
|
||||
On the other platforms, call with `NULL`.
|
||||
|
||||
See `naett.h` for reference docs.
|
||||
|
||||
## Platform implementations
|
||||
|
||||
`naett` uses the following HTTP client libraries on each platform:
|
||||
|
||||
| Platform | Library / component | Build with |
|
||||
| --- | --- | --- |
|
||||
| macOS, iOS | NSURLRequest | -framework Foundation |
|
||||
| Windows | WinHTTP Sessions | -lwinhttp |
|
||||
| Android | java.net.URL | NDK |
|
||||
| Linux | libcurl | -lcurl -lpthread |
|
||||
|
||||
### Example
|
||||
|
||||
```C
|
||||
#include "naett.h"
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
naettInit(NULL);
|
||||
|
||||
naettReq* req =
|
||||
naettRequest("https://foo.site.net", naettMethod("GET"), naettHeader("accept", "application/json"));
|
||||
|
||||
naettRes* res = naettMake(req);
|
||||
|
||||
while (!naettComplete(res)) {
|
||||
usleep(100 * 1000);
|
||||
}
|
||||
|
||||
if (naettGetStatus(res) < 0) {
|
||||
printf("Request failed\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bodyLength = 0;
|
||||
const char* body = naettGetBody(res, &bodyLength);
|
||||
|
||||
printf("Got %d bytes of type '%s':\n", bodyLength, naettGetHeader(res, "Content-Type"));
|
||||
printf("%.100s\n...\n", body);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,145 @@
|
||||
#ifndef LIBNAETT_H
|
||||
#define LIBNAETT_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if __ANDROID__
|
||||
#include <jni.h>
|
||||
typedef JavaVM* naettInitData;
|
||||
#else
|
||||
typedef void* naettInitData;
|
||||
#endif
|
||||
|
||||
#define NAETT_UA "Naett/1.0"
|
||||
|
||||
/**
|
||||
* @brief Global init method.
|
||||
* Call to initialize the library.
|
||||
*/
|
||||
void naettInit(naettInitData initThing);
|
||||
|
||||
typedef struct naettReq naettReq;
|
||||
typedef struct naettRes naettRes;
|
||||
// If naettReadFunc is called with NULL dest, it must respond with the body size
|
||||
typedef int (*naettReadFunc)(void* dest, int bufferSize, void* userData);
|
||||
typedef int (*naettWriteFunc)(const void* source, int bytes, void* userData);
|
||||
typedef int (*naettHeaderLister)(const char* name, const char* value, void* userData);
|
||||
|
||||
// Option to `naettRequest`
|
||||
typedef struct naettOption naettOption;
|
||||
|
||||
// Sets request method. Defaults to "GET".
|
||||
naettOption* naettMethod(const char* method);
|
||||
// Adds a request header.
|
||||
naettOption* naettHeader(const char* name, const char* value);
|
||||
// Sets the request body. Ignored if a body reader is configured.
|
||||
// The body is not copied, and the passed pointer must be valid for the
|
||||
// lifetime of the request.
|
||||
naettOption* naettBody(const char* body, int size);
|
||||
// Sets a request body reader.
|
||||
naettOption* naettBodyReader(naettReadFunc reader, void* userData);
|
||||
// Sets a response body writer.
|
||||
naettOption* naettBodyWriter(naettWriteFunc writer, void* userData);
|
||||
// Sets connection timeout in milliseconds.
|
||||
naettOption* naettTimeout(int milliSeconds);
|
||||
// Sets the user agent.
|
||||
naettOption* naettUserAgent(const char *userAgent);
|
||||
|
||||
/**
|
||||
* @brief Creates a new request to the specified url.
|
||||
* Use varargs options to configure the connection and following request.
|
||||
*/
|
||||
#define naettRequest(url, ...) naettRequest_va(url, countOptions(__VA_ARGS__), ##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @brief Creates a new request to the specified url.
|
||||
* Uses an array of options rather than varargs.
|
||||
*/
|
||||
naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options);
|
||||
|
||||
/**
|
||||
* @brief Makes a request and returns a response object.
|
||||
* The actual request is processed asynchronously, use `naettComplete`
|
||||
* to check if the response is completed.
|
||||
*
|
||||
* A request object can be reused multiple times to make requests, but
|
||||
* there can be only one active request using the same request object.
|
||||
*/
|
||||
naettRes* naettMake(naettReq* request);
|
||||
|
||||
/**
|
||||
* @brief Frees a previously allocated request object.
|
||||
* The request must not have any pending responses.
|
||||
*/
|
||||
void naettFree(naettReq* request);
|
||||
|
||||
/**
|
||||
* @brief Checks if a response is complete, with a result
|
||||
* or with an error.
|
||||
* Use `naettGetStatus` to get the status.
|
||||
*/
|
||||
int naettComplete(const naettRes* response);
|
||||
|
||||
enum naettStatus {
|
||||
naettConnectionError = -1,
|
||||
naettProtocolError = -2,
|
||||
naettReadError = -3,
|
||||
naettWriteError = -4,
|
||||
naettGenericError = -5,
|
||||
naettProcessing = 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Returns the status of a response.
|
||||
* Status codes > 0 are HTTP status codes as returned by the server.
|
||||
* Status codes < 0 are processing errors according to the `naettStatus`
|
||||
* enum values.
|
||||
*/
|
||||
int naettGetStatus(const naettRes* response);
|
||||
|
||||
/**
|
||||
* @brief Returns the response body.
|
||||
* The body returned by this method is always empty when a custom
|
||||
* body reader has been set up using the `naettBodyReader` option.
|
||||
*/
|
||||
const void* naettGetBody(naettRes* response, int* outSize);
|
||||
|
||||
/**
|
||||
* @brief Returns the HTTP header value for the specified header name.
|
||||
*/
|
||||
const char* naettGetHeader(naettRes* response, const char* name);
|
||||
|
||||
/**
|
||||
* @brief Returns how many bytes have been read from the response so far,
|
||||
* and the integer pointed to by totalSize gets the Content-Length if available,
|
||||
* or -1 if not (or 0 if headers have not been read yet).
|
||||
*/
|
||||
int naettGetTotalBytesRead(naettRes* response, int* totalSize);
|
||||
|
||||
/**
|
||||
* @brief Enumerates all response headers as long as the `lister`
|
||||
* returns true.
|
||||
*/
|
||||
void naettListHeaders(naettRes* response, naettHeaderLister lister, void* userData);
|
||||
|
||||
/**
|
||||
* @brief Returns the request that initiated this response
|
||||
*/
|
||||
naettReq* naettGetRequest(naettRes* response);
|
||||
|
||||
/**
|
||||
* @brief Closes a response object.
|
||||
*/
|
||||
void naettClose(naettRes* response);
|
||||
|
||||
// Varargs glue
|
||||
naettReq* naettRequest_va(const char* url, int numOptions, ...);
|
||||
#define countOptions(...) ((sizeof((void*[]){ __VA_ARGS__ }) / sizeof(void*)))
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // LIBNAETT_H
|
||||
@@ -0,0 +1,281 @@
|
||||
#include "naett_internal.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
|
||||
#ifdef __cplusplus
|
||||
#error "Cannot build in C++ mode"
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include <pthread.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "naett", __VA_ARGS__))
|
||||
#else
|
||||
#define LOGD(...) ((void)0)
|
||||
#endif
|
||||
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "naett", __VA_ARGS__))
|
||||
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "naett", __VA_ARGS__))
|
||||
|
||||
static JavaVM* globalVM = NULL;
|
||||
|
||||
static JavaVM* getVM() {
|
||||
if (globalVM == NULL) {
|
||||
LOGE("Panic: No VM configured, exiting.");
|
||||
exit(42);
|
||||
}
|
||||
return globalVM;
|
||||
}
|
||||
|
||||
static JNIEnv* getEnv() {
|
||||
JavaVM* vm = getVM();
|
||||
JNIEnv* env;
|
||||
(*vm)->AttachCurrentThread(vm, &env, NULL);
|
||||
return env;
|
||||
}
|
||||
|
||||
static int catch (JNIEnv* env) {
|
||||
int thrown = (*env)->ExceptionCheck(env);
|
||||
if (thrown) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
}
|
||||
return thrown;
|
||||
}
|
||||
|
||||
static jmethodID getMethod(JNIEnv* env, jobject instance, const char* method, const char* sig) {
|
||||
jclass clazz = (*env)->GetObjectClass(env, instance);
|
||||
jmethodID id = (*env)->GetMethodID(env, clazz, method, sig);
|
||||
(*env)->DeleteLocalRef(env, clazz);
|
||||
return id;
|
||||
}
|
||||
|
||||
static jobject call(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) {
|
||||
jmethodID methodID = getMethod(env, instance, method, sig);
|
||||
va_list args;
|
||||
va_start(args, sig);
|
||||
jobject result = (*env)->CallObjectMethodV(env, instance, methodID, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void voidCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) {
|
||||
jmethodID methodID = getMethod(env, instance, method, sig);
|
||||
va_list args;
|
||||
va_start(args, sig);
|
||||
(*env)->CallVoidMethodV(env, instance, methodID, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
static jint intCall(JNIEnv* env, jobject instance, const char* method, const char* sig, ...) {
|
||||
jmethodID methodID = getMethod(env, instance, method, sig);
|
||||
va_list args;
|
||||
va_start(args, sig);
|
||||
jint result = (*env)->CallIntMethodV(env, instance, methodID, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
void naettPlatformInit(naettInitData initData) {
|
||||
globalVM = initData;
|
||||
}
|
||||
|
||||
int naettPlatformInitRequest(InternalRequest* req) {
|
||||
JNIEnv* env = getEnv();
|
||||
(*env)->PushLocalFrame(env, 10);
|
||||
jclass URL = (*env)->FindClass(env, "java/net/URL");
|
||||
jmethodID newURL = (*env)->GetMethodID(env, URL, "<init>", "(Ljava/lang/String;)V");
|
||||
jstring urlString = (*env)->NewStringUTF(env, req->url);
|
||||
jobject url = (*env)->NewObject(env, URL, newURL, urlString);
|
||||
if (catch (env)) {
|
||||
(*env)->PopLocalFrame(env, NULL);
|
||||
return 0;
|
||||
}
|
||||
req->urlObject = (*env)->NewGlobalRef(env, url);
|
||||
(*env)->PopLocalFrame(env, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void* processRequest(void* data) {
|
||||
const int bufSize = 10240;
|
||||
char byteBuffer[bufSize];
|
||||
InternalResponse* res = (InternalResponse*)data;
|
||||
InternalRequest* req = res->request;
|
||||
|
||||
JNIEnv* env = getEnv();
|
||||
(*env)->PushLocalFrame(env, 100);
|
||||
|
||||
jobject connection = call(env, req->urlObject, "openConnection", "()Ljava/net/URLConnection;");
|
||||
if (catch (env)) {
|
||||
res->code = naettConnectionError;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
{
|
||||
jstring name = (*env)->NewStringUTF(env, "User-Agent");
|
||||
jstring value = (*env)->NewStringUTF(env, req->options.userAgent ? req->options.userAgent : NAETT_UA);
|
||||
voidCall(env, connection, "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V", name, value);
|
||||
(*env)->DeleteLocalRef(env, name);
|
||||
(*env)->DeleteLocalRef(env, value);
|
||||
}
|
||||
|
||||
KVLink* header = req->options.headers;
|
||||
while (header != NULL) {
|
||||
jstring name = (*env)->NewStringUTF(env, header->key);
|
||||
jstring value = (*env)->NewStringUTF(env, header->value);
|
||||
voidCall(env, connection, "addRequestProperty", "(Ljava/lang/String;Ljava/lang/String;)V", name, value);
|
||||
(*env)->DeleteLocalRef(env, name);
|
||||
(*env)->DeleteLocalRef(env, value);
|
||||
header = header->next;
|
||||
}
|
||||
|
||||
jobject outputStream = NULL;
|
||||
if (strcmp(req->options.method, "POST") == 0 || strcmp(req->options.method, "PUT") == 0 ||
|
||||
strcmp(req->options.method, "PATCH") == 0 || strcmp(req->options.method, "DELETE") == 0) {
|
||||
voidCall(env, connection, "setDoOutput", "(Z)V", 1);
|
||||
outputStream = call(env, connection, "getOutputStream", "()Ljava/io/OutputStream;");
|
||||
}
|
||||
jobject methodString = (*env)->NewStringUTF(env, req->options.method);
|
||||
voidCall(env, connection, "setRequestMethod", "(Ljava/lang/String;)V", methodString);
|
||||
voidCall(env, connection, "setConnectTimeout", "(I)V", req->options.timeoutMS);
|
||||
voidCall(env, connection, "setInstanceFollowRedirects", "(Z)V", 1);
|
||||
|
||||
voidCall(env, connection, "connect", "()V");
|
||||
if (catch (env)) {
|
||||
res->code = naettConnectionError;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
jbyteArray buffer = (*env)->NewByteArray(env, bufSize);
|
||||
|
||||
if (outputStream != NULL) {
|
||||
int bytesRead = 0;
|
||||
if (req->options.bodyReader != NULL)
|
||||
do {
|
||||
bytesRead = req->options.bodyReader(byteBuffer, bufSize, req->options.bodyReaderData);
|
||||
if (bytesRead > 0) {
|
||||
(*env)->SetByteArrayRegion(env, buffer, 0, bytesRead, (const jbyte*) byteBuffer);
|
||||
voidCall(env, outputStream, "write", "([BII)V", buffer, 0, bytesRead);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} while (!res->closeRequested);
|
||||
voidCall(env, outputStream, "close", "()V");
|
||||
}
|
||||
|
||||
jobject headerMap = call(env, connection, "getHeaderFields", "()Ljava/util/Map;");
|
||||
if (catch (env)) {
|
||||
res->code = naettProtocolError;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
jobject headerSet = call(env, headerMap, "keySet", "()Ljava/util/Set;");
|
||||
jarray headers = call(env, headerSet, "toArray", "()[Ljava/lang/Object;");
|
||||
jsize headerCount = (*env)->GetArrayLength(env, headers);
|
||||
|
||||
KVLink *firstHeader = NULL;
|
||||
for (int i = 0; i < headerCount; i++) {
|
||||
jstring name = (*env)->GetObjectArrayElement(env, headers, i);
|
||||
if (name == NULL) {
|
||||
continue;
|
||||
}
|
||||
const char* nameString = (*env)->GetStringUTFChars(env, name, NULL);
|
||||
|
||||
jobject values = call(env, headerMap, "get", "(Ljava/lang/Object;)Ljava/lang/Object;", name);
|
||||
jstring value = call(env, values, "get", "(I)Ljava/lang/Object;", 0);
|
||||
const char* valueString = (*env)->GetStringUTFChars(env, value, NULL);
|
||||
|
||||
naettAlloc(KVLink, node);
|
||||
node->key = strdup(nameString);
|
||||
node->value = strdup(valueString);
|
||||
node->next = firstHeader;
|
||||
firstHeader = node;
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, name, nameString);
|
||||
(*env)->ReleaseStringUTFChars(env, value, valueString);
|
||||
|
||||
(*env)->DeleteLocalRef(env, name);
|
||||
(*env)->DeleteLocalRef(env, value);
|
||||
(*env)->DeleteLocalRef(env, values);
|
||||
}
|
||||
res->headers = firstHeader;
|
||||
|
||||
const char *contentLength = naettGetHeader((naettRes *)res, "Content-Length");
|
||||
if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) {
|
||||
res->contentLength = -1;
|
||||
}
|
||||
|
||||
int statusCode = intCall(env, connection, "getResponseCode", "()I");
|
||||
|
||||
jobject inputStream = NULL;
|
||||
|
||||
if (statusCode >= 400) {
|
||||
inputStream = call(env, connection, "getErrorStream", "()Ljava/io/InputStream;");
|
||||
} else {
|
||||
inputStream = call(env, connection, "getInputStream", "()Ljava/io/InputStream;");
|
||||
}
|
||||
if (catch (env)) {
|
||||
res->code = naettProtocolError;
|
||||
goto finally;
|
||||
}
|
||||
|
||||
jint bytesRead = 0;
|
||||
do {
|
||||
bytesRead = intCall(env, inputStream, "read", "([B)I", buffer);
|
||||
if (catch (env)) {
|
||||
res->code = naettReadError;
|
||||
goto finally;
|
||||
}
|
||||
if (bytesRead < 0) {
|
||||
break;
|
||||
} else if (bytesRead > 0) {
|
||||
(*env)->GetByteArrayRegion(env, buffer, 0, bytesRead, (jbyte*) byteBuffer);
|
||||
req->options.bodyWriter(byteBuffer, bytesRead, req->options.bodyWriterData);
|
||||
res->totalBytesRead += bytesRead;
|
||||
}
|
||||
} while (!res->closeRequested);
|
||||
|
||||
voidCall(env, inputStream, "close", "()V");
|
||||
|
||||
res->code = statusCode;
|
||||
|
||||
finally:
|
||||
res->complete = 1;
|
||||
(*env)->PopLocalFrame(env, NULL);
|
||||
JavaVM* vm = getVM();
|
||||
(*env)->ExceptionClear(env);
|
||||
(*vm)->DetachCurrentThread(vm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void startWorkerThread(InternalResponse* res) {
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
|
||||
pthread_create(&res->workerThread, &attr, processRequest, res);
|
||||
pthread_setname_np(res->workerThread, "naett worker thread");
|
||||
}
|
||||
|
||||
void naettPlatformMakeRequest(InternalResponse* res) {
|
||||
startWorkerThread(res);
|
||||
}
|
||||
|
||||
void naettPlatformFreeRequest(InternalRequest* req) {
|
||||
JNIEnv* env = getEnv();
|
||||
(*env)->DeleteGlobalRef(env, req->urlObject);
|
||||
}
|
||||
|
||||
void naettPlatformCloseResponse(InternalResponse* res) {
|
||||
res->closeRequested = 1;
|
||||
if (res->workerThread != 0) {
|
||||
int joinResult = pthread_join(res->workerThread, NULL);
|
||||
if (joinResult != 0) {
|
||||
LOGE("Failed to join: %s", strerror(joinResult));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // __ANDROID__
|
||||
@@ -0,0 +1,405 @@
|
||||
#include "naett_internal.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
typedef struct InternalParam* InternalParamPtr;
|
||||
typedef void (*ParamSetter)(InternalParamPtr param, InternalRequest* req);
|
||||
|
||||
typedef struct InternalParam {
|
||||
ParamSetter setter;
|
||||
int offset;
|
||||
union {
|
||||
int integer;
|
||||
const char* string;
|
||||
struct {
|
||||
const char* key;
|
||||
const char* value;
|
||||
} kv;
|
||||
void* ptr;
|
||||
void (*func)(void);
|
||||
};
|
||||
} InternalParam;
|
||||
|
||||
typedef struct InternalOption {
|
||||
#define maxParams 2
|
||||
int numParams;
|
||||
InternalParam params[maxParams];
|
||||
} InternalOption;
|
||||
|
||||
static void stringSetter(InternalParamPtr param, InternalRequest* req) {
|
||||
char* stringCopy = strdup(param->string);
|
||||
char* opaque = (char*)&req->options;
|
||||
char** stringField = (char**)(opaque + param->offset);
|
||||
if (*stringField) {
|
||||
free(*stringField);
|
||||
}
|
||||
*stringField = stringCopy;
|
||||
}
|
||||
|
||||
static void intSetter(InternalParamPtr param, InternalRequest* req) {
|
||||
char* opaque = (char*)&req->options;
|
||||
int* intField = (int*)(opaque + param->offset);
|
||||
*intField = param->integer;
|
||||
}
|
||||
|
||||
static void ptrSetter(InternalParamPtr param, InternalRequest* req) {
|
||||
char* opaque = (char*)&req->options;
|
||||
void** ptrField = (void**)(opaque + param->offset);
|
||||
*ptrField = param->ptr;
|
||||
}
|
||||
|
||||
static void kvSetter(InternalParamPtr param, InternalRequest* req) {
|
||||
char* opaque = (char*)&req->options;
|
||||
KVLink** kvField = (KVLink**)(opaque + param->offset);
|
||||
|
||||
naettAlloc(KVLink, newNode);
|
||||
newNode->key = strdup(param->kv.key);
|
||||
newNode->value = strdup(param->kv.value);
|
||||
newNode->next = *kvField;
|
||||
|
||||
*kvField = newNode;
|
||||
}
|
||||
|
||||
static int defaultBodyReader(void* dest, int bufferSize, void* userData) {
|
||||
Buffer* buffer = (Buffer*) userData;
|
||||
|
||||
if (dest == NULL) {
|
||||
return buffer->size;
|
||||
}
|
||||
|
||||
int bytesToRead = buffer->size - buffer->position;
|
||||
if (bytesToRead > bufferSize) {
|
||||
bytesToRead = bufferSize;
|
||||
}
|
||||
|
||||
const char* source = ((const char*)buffer->data) + buffer->position;
|
||||
memcpy(dest, source, bytesToRead);
|
||||
buffer->position += bytesToRead;
|
||||
return bytesToRead;
|
||||
}
|
||||
|
||||
static int defaultBodyWriter(const void* source, int bytes, void* userData) {
|
||||
Buffer* buffer = (Buffer*) userData;
|
||||
int newCapacity = buffer->capacity;
|
||||
if (newCapacity == 0) {
|
||||
newCapacity = bytes;
|
||||
}
|
||||
while (newCapacity - buffer->size < bytes) {
|
||||
newCapacity *= 2;
|
||||
}
|
||||
if (newCapacity != buffer->capacity) {
|
||||
buffer->data = realloc(buffer->data, newCapacity);
|
||||
buffer->capacity = newCapacity;
|
||||
}
|
||||
char* dest = ((char*)buffer->data) + buffer->size;
|
||||
memcpy(dest, source, bytes);
|
||||
buffer->size += bytes;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static int initialized = 0;
|
||||
|
||||
static void initRequest(InternalRequest* req, const char* url) {
|
||||
assert(initialized);
|
||||
req->options.method = strdup("GET");
|
||||
req->options.timeoutMS = 5000;
|
||||
req->url = strdup(url);
|
||||
}
|
||||
|
||||
static void applyOptionParams(InternalRequest* req, InternalOption* option) {
|
||||
for (int j = 0; j < option->numParams; j++) {
|
||||
InternalParam* param = option->params + j;
|
||||
param->setter(param, req);
|
||||
}
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
void naettInit(naettInitData initData) {
|
||||
assert(!initialized);
|
||||
naettPlatformInit(initData);
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
naettOption* naettMethod(const char* method) {
|
||||
naettAlloc(InternalOption, option);
|
||||
option->numParams = 1;
|
||||
InternalParam* param = &option->params[0];
|
||||
|
||||
param->string = method;
|
||||
param->offset = offsetof(RequestOptions, method);
|
||||
param->setter = stringSetter;
|
||||
|
||||
return (naettOption*)option;
|
||||
}
|
||||
|
||||
naettOption* naettHeader(const char* name, const char* value) {
|
||||
naettAlloc(InternalOption, option);
|
||||
option->numParams = 1;
|
||||
InternalParam* param = &option->params[0];
|
||||
|
||||
param->kv.key = name;
|
||||
param->kv.value = value;
|
||||
param->offset = offsetof(RequestOptions, headers);
|
||||
param->setter = kvSetter;
|
||||
|
||||
return (naettOption*)option;
|
||||
}
|
||||
|
||||
naettOption* naettUserAgent(const char* userAgent) {
|
||||
naettAlloc(InternalOption, option);
|
||||
option->numParams = 1;
|
||||
InternalParam* param = &option->params[0];
|
||||
|
||||
param->string = userAgent;
|
||||
param->offset = offsetof(RequestOptions, userAgent);
|
||||
param->setter = stringSetter;
|
||||
|
||||
return (naettOption*)option;
|
||||
}
|
||||
|
||||
naettOption* naettTimeout(int timeoutMS) {
|
||||
naettAlloc(InternalOption, option);
|
||||
option->numParams = 1;
|
||||
InternalParam* param = &option->params[0];
|
||||
|
||||
param->integer = timeoutMS;
|
||||
param->offset = offsetof(RequestOptions, timeoutMS);
|
||||
param->setter = intSetter;
|
||||
|
||||
return (naettOption*)option;
|
||||
}
|
||||
|
||||
naettOption* naettBody(const char* body, int size) {
|
||||
naettAlloc(InternalOption, option);
|
||||
option->numParams = 2;
|
||||
|
||||
InternalParam* bodyParam = &option->params[0];
|
||||
InternalParam* sizeParam = &option->params[1];
|
||||
|
||||
bodyParam->ptr = (void*)body;
|
||||
bodyParam->offset = offsetof(RequestOptions, body) + offsetof(Buffer, data);
|
||||
bodyParam->setter = ptrSetter;
|
||||
|
||||
sizeParam->integer = size;
|
||||
sizeParam->offset = offsetof(RequestOptions, body) + offsetof(Buffer, size);
|
||||
sizeParam->setter = intSetter;
|
||||
|
||||
return (naettOption*)option;
|
||||
}
|
||||
|
||||
naettOption* naettBodyReader(naettReadFunc reader, void* userData) {
|
||||
naettAlloc(InternalOption, option);
|
||||
option->numParams = 2;
|
||||
|
||||
InternalParam* readerParam = &option->params[0];
|
||||
InternalParam* dataParam = &option->params[1];
|
||||
|
||||
readerParam->func = (void (*)(void)) reader;
|
||||
readerParam->offset = offsetof(RequestOptions, bodyReader);
|
||||
readerParam->setter = ptrSetter;
|
||||
|
||||
dataParam->ptr = userData;
|
||||
dataParam->offset = offsetof(RequestOptions, bodyReaderData);
|
||||
dataParam->setter = ptrSetter;
|
||||
|
||||
return (naettOption*)option;
|
||||
}
|
||||
|
||||
naettOption* naettBodyWriter(naettWriteFunc writer, void* userData) {
|
||||
naettAlloc(InternalOption, option);
|
||||
option->numParams = 2;
|
||||
|
||||
InternalParam* writerParam = &option->params[0];
|
||||
InternalParam* dataParam = &option->params[1];
|
||||
|
||||
writerParam->func = (void(*)(void)) writer;
|
||||
writerParam->offset = offsetof(RequestOptions, bodyWriter);
|
||||
writerParam->setter = ptrSetter;
|
||||
|
||||
dataParam->ptr = userData;
|
||||
dataParam->offset = offsetof(RequestOptions, bodyWriterData);
|
||||
dataParam->setter = ptrSetter;
|
||||
|
||||
return (naettOption*)option;
|
||||
}
|
||||
|
||||
void setupDefaultRW(InternalRequest* req) {
|
||||
if (req->options.bodyReader == NULL) {
|
||||
req->options.bodyReader = defaultBodyReader;
|
||||
req->options.bodyReaderData = (void*) &req->options.body;
|
||||
}
|
||||
if (req->options.bodyReader == defaultBodyReader) {
|
||||
req->options.body.position = 0;
|
||||
}
|
||||
if (req->options.bodyWriter == NULL) {
|
||||
req->options.bodyWriter = defaultBodyWriter;
|
||||
}
|
||||
}
|
||||
|
||||
naettReq* naettRequest_va(const char* url, int numArgs, ...) {
|
||||
assert(url != NULL);
|
||||
|
||||
va_list args;
|
||||
InternalOption* option;
|
||||
naettAlloc(InternalRequest, req);
|
||||
initRequest(req, url);
|
||||
|
||||
va_start(args, numArgs);
|
||||
for (int i = 0; i < numArgs; i++) {
|
||||
option = va_arg(args, InternalOption*);
|
||||
applyOptionParams(req, option);
|
||||
free(option);
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
setupDefaultRW(req);
|
||||
|
||||
if (naettPlatformInitRequest(req)) {
|
||||
return (naettReq*)req;
|
||||
}
|
||||
|
||||
naettFree((naettReq*) req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
naettReq* naettRequestWithOptions(const char* url, int numOptions, const naettOption** options) {
|
||||
assert(url != NULL);
|
||||
assert(numOptions == 0 || options != NULL);
|
||||
|
||||
naettAlloc(InternalRequest, req);
|
||||
initRequest(req, url);
|
||||
|
||||
for (int i = 0; i < numOptions; i++) {
|
||||
InternalOption* option = (InternalOption*)options[i];
|
||||
applyOptionParams(req, option);
|
||||
free(option);
|
||||
}
|
||||
|
||||
setupDefaultRW(req);
|
||||
|
||||
if (naettPlatformInitRequest(req)) {
|
||||
return (naettReq*)req;
|
||||
}
|
||||
|
||||
naettFree((naettReq*) req);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
naettRes* naettMake(naettReq* request) {
|
||||
assert(initialized);
|
||||
assert(request != NULL);
|
||||
|
||||
InternalRequest* req = (InternalRequest*)request;
|
||||
naettAlloc(InternalResponse, res);
|
||||
res->request = req;
|
||||
|
||||
if (req->options.bodyWriter == defaultBodyWriter) {
|
||||
req->options.bodyWriterData = (void*) &res->body;
|
||||
}
|
||||
|
||||
naettPlatformMakeRequest(res);
|
||||
return (naettRes*) res;
|
||||
}
|
||||
|
||||
const void* naettGetBody(naettRes* response, int* size) {
|
||||
assert(response != NULL);
|
||||
assert(size != NULL);
|
||||
|
||||
InternalResponse* res = (InternalResponse*)response;
|
||||
*size = res->body.size;
|
||||
return res->body.data;
|
||||
}
|
||||
|
||||
int naettGetTotalBytesRead(naettRes* response, int* totalSize) {
|
||||
assert(response != NULL);
|
||||
assert(totalSize != NULL);
|
||||
|
||||
InternalResponse* res = (InternalResponse*)response;
|
||||
*totalSize = res->contentLength;
|
||||
return res->totalBytesRead;
|
||||
}
|
||||
|
||||
const char* naettGetHeader(naettRes* response, const char* name) {
|
||||
assert(response != NULL);
|
||||
assert(name != NULL);
|
||||
|
||||
InternalResponse* res = (InternalResponse*)response;
|
||||
KVLink* node = res->headers;
|
||||
while (node) {
|
||||
if (strcasecmp(name, node->key) == 0) {
|
||||
return node->value;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void naettListHeaders(naettRes* response, naettHeaderLister lister, void* userData) {
|
||||
assert(response != NULL);
|
||||
assert(lister != NULL);
|
||||
|
||||
InternalResponse* res = (InternalResponse*)response;
|
||||
KVLink* node = res->headers;
|
||||
while (node) {
|
||||
if (!lister(node->key, node->value, userData)) {
|
||||
return;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
|
||||
naettReq* naettGetRequest(naettRes* response) {
|
||||
assert(response != NULL);
|
||||
InternalResponse* res = (InternalResponse*)response;
|
||||
return (naettReq*) res->request;
|
||||
}
|
||||
|
||||
int naettComplete(const naettRes* response) {
|
||||
assert(response != NULL);
|
||||
InternalResponse* res = (InternalResponse*)response;
|
||||
return res->complete;
|
||||
}
|
||||
|
||||
int naettGetStatus(const naettRes* response) {
|
||||
assert(response != NULL);
|
||||
InternalResponse* res = (InternalResponse*)response;
|
||||
return res->code;
|
||||
}
|
||||
|
||||
static void freeKVList(KVLink* node) {
|
||||
while (node != NULL) {
|
||||
free((void*) node->key);
|
||||
free((void*) node->value);
|
||||
KVLink* next = node->next;
|
||||
free(node);
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
void naettFree(naettReq* request) {
|
||||
assert(request != NULL);
|
||||
|
||||
InternalRequest* req = (InternalRequest*)request;
|
||||
naettPlatformFreeRequest(req);
|
||||
KVLink* node = req->options.headers;
|
||||
freeKVList(node);
|
||||
free((void*)req->options.method);
|
||||
free((void*)req->url);
|
||||
free(request);
|
||||
}
|
||||
|
||||
void naettClose(naettRes* response) {
|
||||
assert(response != NULL);
|
||||
|
||||
InternalResponse* res = (InternalResponse*)response;
|
||||
res->request = NULL;
|
||||
naettPlatformCloseResponse(res);
|
||||
KVLink* node = res->headers;
|
||||
freeKVList(node);
|
||||
free(res->body.data);
|
||||
free(response);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
#ifndef NAETT_INTERNAL_H
|
||||
#define NAETT_INTERNAL_H
|
||||
|
||||
// PPSSPP: The upstream amalgamated build (naett.c) included naett.h before this header.
|
||||
// We build the sources directly, so include it here instead.
|
||||
#include "../naett.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define strcasecmp _stricmp
|
||||
#undef strdup
|
||||
#define strdup _strdup
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <windows.h>
|
||||
#include <winhttp.h>
|
||||
#ifndef min
|
||||
#define min(x,y) ((x) < (y) ? (x) : (y))
|
||||
#endif
|
||||
#define __WINDOWS__ 1
|
||||
#endif
|
||||
|
||||
#if __linux__ && !__ANDROID__
|
||||
#define __LINUX__ 1
|
||||
#include <curl/curl.h>
|
||||
#endif
|
||||
|
||||
#if __ANDROID__
|
||||
#include <jni.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "TargetConditionals.h"
|
||||
#include <objc/objc.h>
|
||||
#if TARGET_OS_IPHONE
|
||||
#define __IOS__ 1
|
||||
#else
|
||||
#define __MACOS__ 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define naettAlloc(TYPE, VAR) TYPE* VAR = (TYPE*)calloc(1, sizeof(TYPE))
|
||||
|
||||
typedef struct KVLink {
|
||||
const char* key;
|
||||
const char* value;
|
||||
struct KVLink* next;
|
||||
} KVLink;
|
||||
|
||||
typedef struct Buffer {
|
||||
void* data;
|
||||
int size;
|
||||
int capacity;
|
||||
int position;
|
||||
} Buffer;
|
||||
|
||||
typedef struct {
|
||||
const char* method;
|
||||
const char* userAgent;
|
||||
int timeoutMS;
|
||||
naettReadFunc bodyReader;
|
||||
void* bodyReaderData;
|
||||
naettWriteFunc bodyWriter;
|
||||
void* bodyWriterData;
|
||||
KVLink* headers;
|
||||
Buffer body;
|
||||
} RequestOptions;
|
||||
|
||||
typedef struct {
|
||||
RequestOptions options;
|
||||
const char* url;
|
||||
#if __APPLE__
|
||||
id urlRequest;
|
||||
#endif
|
||||
#if __ANDROID__
|
||||
jobject urlObject;
|
||||
#endif
|
||||
#if __WINDOWS__
|
||||
HINTERNET session;
|
||||
HINTERNET connection;
|
||||
HINTERNET request;
|
||||
LPWSTR host;
|
||||
LPWSTR resource;
|
||||
#endif
|
||||
} InternalRequest;
|
||||
|
||||
typedef struct {
|
||||
InternalRequest* request;
|
||||
int code;
|
||||
int complete;
|
||||
KVLink* headers;
|
||||
Buffer body;
|
||||
int contentLength; // 0 if headers not read, -1 if Content-Length missing.
|
||||
int totalBytesRead;
|
||||
#if __APPLE__
|
||||
id session;
|
||||
#endif
|
||||
#if __ANDROID__
|
||||
pthread_t workerThread;
|
||||
int closeRequested;
|
||||
#endif
|
||||
#if __LINUX__
|
||||
struct curl_slist* headerList;
|
||||
#endif
|
||||
#if __WINDOWS__
|
||||
char buffer[10240];
|
||||
size_t bytesLeft;
|
||||
#endif
|
||||
} InternalResponse;
|
||||
|
||||
void naettPlatformInit(naettInitData initData);
|
||||
int naettPlatformInitRequest(InternalRequest* req);
|
||||
void naettPlatformMakeRequest(InternalResponse* res);
|
||||
void naettPlatformFreeRequest(InternalRequest* req);
|
||||
void naettPlatformCloseResponse(InternalResponse* res);
|
||||
|
||||
#endif // NAETT_INTERNAL_H
|
||||
@@ -0,0 +1,234 @@
|
||||
#include "naett_internal.h"
|
||||
|
||||
#if __linux__ && !__ANDROID__
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static pthread_t workerThread;
|
||||
static int handleReadFD = 0;
|
||||
static int handleWriteFD = 0;
|
||||
|
||||
static void panic(const char* message) {
|
||||
fprintf(stderr, "%s\n", message);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void* curlWorker(void* data) {
|
||||
CURLM* mc = (CURLM*)data;
|
||||
int activeHandles = 0;
|
||||
int messagesLeft = 1;
|
||||
|
||||
struct curl_waitfd readFd = { handleReadFD, CURL_WAIT_POLLIN };
|
||||
|
||||
union {
|
||||
CURL* handle;
|
||||
char buf[sizeof(CURL*)];
|
||||
} newHandle;
|
||||
|
||||
int newHandlePos = 0;
|
||||
|
||||
while (1) {
|
||||
int status = curl_multi_perform(mc, &activeHandles);
|
||||
if (status != CURLM_OK) {
|
||||
panic("CURL processing failure");
|
||||
}
|
||||
|
||||
struct CURLMsg* message = curl_multi_info_read(mc, &messagesLeft);
|
||||
if (message && message->msg == CURLMSG_DONE) {
|
||||
CURL* handle = message->easy_handle;
|
||||
InternalResponse* res = NULL;
|
||||
curl_easy_getinfo(handle, CURLINFO_PRIVATE, (char**)&res);
|
||||
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &res->code);
|
||||
res->complete = 1;
|
||||
curl_easy_cleanup(handle);
|
||||
}
|
||||
|
||||
|
||||
int readyFDs = 0;
|
||||
curl_multi_wait(mc, &readFd, 1, 1, &readyFDs);
|
||||
|
||||
if (readyFDs == 0 && activeHandles == 0 && messagesLeft == 0) {
|
||||
usleep(100 * 1000);
|
||||
}
|
||||
|
||||
int bytesRead = read(handleReadFD, newHandle.buf, sizeof(newHandle.buf) - newHandlePos);
|
||||
if (bytesRead > 0) {
|
||||
newHandlePos += bytesRead;
|
||||
}
|
||||
if (newHandlePos == sizeof(newHandle.buf)) {
|
||||
curl_multi_add_handle(mc, newHandle.handle);
|
||||
newHandlePos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void naettPlatformInit(naettInitData initData) {
|
||||
curl_global_init(CURL_GLOBAL_ALL);
|
||||
CURLM* mc = curl_multi_init();
|
||||
int fds[2];
|
||||
if (pipe(fds) != 0) {
|
||||
panic("Failed to open pipe");
|
||||
}
|
||||
handleReadFD = fds[0];
|
||||
handleWriteFD = fds[1];
|
||||
|
||||
int flags = fcntl(handleReadFD, F_GETFL, 0);
|
||||
fcntl(handleReadFD, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
pthread_create(&workerThread, &attr, curlWorker, mc);
|
||||
}
|
||||
|
||||
int naettPlatformInitRequest(InternalRequest* req) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t readCallback(char* buffer, size_t size, size_t numItems, void* userData) {
|
||||
InternalResponse* res = (InternalResponse*)userData;
|
||||
InternalRequest* req = res->request;
|
||||
return req->options.bodyReader(buffer, size * numItems, req->options.bodyReaderData);
|
||||
}
|
||||
|
||||
static size_t writeCallback(char* ptr, size_t size, size_t numItems, void* userData) {
|
||||
InternalResponse* res = (InternalResponse*)userData;
|
||||
InternalRequest* req = res->request;
|
||||
size_t bytesWritten = req->options.bodyWriter(ptr, size * numItems, req->options.bodyWriterData);
|
||||
res->totalBytesRead += bytesWritten;
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
#define METHOD(A, B, C) (((A) << 16) | ((B) << 8) | (C))
|
||||
|
||||
static void setupMethod(CURL* curl, const char* method) {
|
||||
if (strlen(method) < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
int methodCode = (method[0] << 16) | (method[1] << 8) | method[2];
|
||||
|
||||
switch (methodCode) {
|
||||
case METHOD('G', 'E', 'T'):
|
||||
case METHOD('C', 'O', 'N'):
|
||||
case METHOD('O', 'P', 'T'):
|
||||
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
|
||||
break;
|
||||
case METHOD('P', 'O', 'S'):
|
||||
case METHOD('P', 'A', 'T'):
|
||||
case METHOD('D', 'E', 'L'):
|
||||
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
||||
break;
|
||||
case METHOD('P', 'U', 'T'):
|
||||
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1);
|
||||
break;
|
||||
case METHOD('H', 'E', 'A'):
|
||||
case METHOD('T', 'R', 'A'):
|
||||
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method);
|
||||
}
|
||||
|
||||
static size_t headerCallback(char* buffer, size_t size, size_t nitems, void* userData) {
|
||||
InternalResponse* res = (InternalResponse*) userData;
|
||||
size_t headerSize = size * nitems;
|
||||
|
||||
char* headerName = strndup(buffer, headerSize);
|
||||
char* split = strchr(headerName, ':');
|
||||
if (split) {
|
||||
*split = 0;
|
||||
split++;
|
||||
while (*split == ' ') {
|
||||
split++;
|
||||
}
|
||||
char* headerValue = strdup(split);
|
||||
|
||||
char* cr = strchr(headerValue, 13);
|
||||
if (cr) {
|
||||
*cr = 0;
|
||||
}
|
||||
|
||||
char* lf = strchr(headerValue, 10);
|
||||
if (lf) {
|
||||
*lf = 0;
|
||||
}
|
||||
|
||||
naettAlloc(KVLink, node);
|
||||
node->next = res->headers;
|
||||
node->key = headerName;
|
||||
node->value = headerValue;
|
||||
res->headers = node;
|
||||
}
|
||||
|
||||
return headerSize;
|
||||
}
|
||||
|
||||
void naettPlatformMakeRequest(InternalResponse* res) {
|
||||
InternalRequest* req = res->request;
|
||||
|
||||
CURL* c = curl_easy_init();
|
||||
curl_easy_setopt(c, CURLOPT_URL, req->url);
|
||||
curl_easy_setopt(c, CURLOPT_CONNECTTIMEOUT_MS, req->options.timeoutMS);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_READFUNCTION, readCallback);
|
||||
curl_easy_setopt(c, CURLOPT_READDATA, res);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, writeCallback);
|
||||
curl_easy_setopt(c, CURLOPT_WRITEDATA, res);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, headerCallback);
|
||||
curl_easy_setopt(c, CURLOPT_HEADERDATA, res);
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1);
|
||||
|
||||
int bodySize = res->request->options.bodyReader(NULL, 0, res->request->options.bodyReaderData);
|
||||
curl_easy_setopt(c, CURLOPT_POSTFIELDSIZE, bodySize);
|
||||
|
||||
setupMethod(c, req->options.method);
|
||||
|
||||
struct curl_slist* headerList = NULL;
|
||||
char uaBuf[512];
|
||||
snprintf(uaBuf, sizeof(uaBuf), "User-Agent: %s", req->options.userAgent ? req->options.userAgent : NAETT_UA);
|
||||
headerList = curl_slist_append(headerList, uaBuf);
|
||||
|
||||
KVLink* header = req->options.headers;
|
||||
size_t bufferSize = 0;
|
||||
char* buffer = NULL;
|
||||
while (header) {
|
||||
size_t headerLength = strlen(header->key) + strlen(header->value) + 1 + 1; // colon + null
|
||||
if (headerLength > bufferSize) {
|
||||
bufferSize = headerLength;
|
||||
buffer = (char*)realloc(buffer, bufferSize);
|
||||
}
|
||||
snprintf(buffer, bufferSize, "%s:%s", header->key, header->value);
|
||||
headerList = curl_slist_append(headerList, buffer);
|
||||
header = header->next;
|
||||
}
|
||||
curl_easy_setopt(c, CURLOPT_HTTPHEADER, headerList);
|
||||
free(buffer);
|
||||
res->headerList = headerList;
|
||||
|
||||
curl_easy_setopt(c, CURLOPT_PRIVATE, res);
|
||||
|
||||
write(handleWriteFD, &c, sizeof(c));
|
||||
}
|
||||
|
||||
void naettPlatformFreeRequest(InternalRequest* req) {
|
||||
}
|
||||
|
||||
void naettPlatformCloseResponse(InternalResponse* res) {
|
||||
curl_slist_free_all(res->headerList);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
#ifndef NAETT_OBJC_H
|
||||
#define NAETT_OBJC_H
|
||||
|
||||
#if defined(__IOS__) || defined (__MACOS__)
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <objc/NSObjCRuntime.h>
|
||||
#include <objc/message.h>
|
||||
#include <objc/objc.h>
|
||||
#include <objc/runtime.h>
|
||||
|
||||
#if defined(__OBJC__) && __has_feature(objc_arc)
|
||||
#error "ARC is not supported"
|
||||
#endif
|
||||
|
||||
// ABI is a bit different between platforms
|
||||
#ifdef __arm64__
|
||||
#define abi_objc_msgSend_stret objc_msgSend
|
||||
#else
|
||||
#define abi_objc_msgSend_stret objc_msgSend_stret
|
||||
#endif
|
||||
#ifdef __i386__
|
||||
#define abi_objc_msgSend_fpret objc_msgSend_fpret
|
||||
#else
|
||||
#define abi_objc_msgSend_fpret objc_msgSend
|
||||
#endif
|
||||
|
||||
#define objc_msgSendSuper_t(RET, ...) ((RET(*)(struct objc_super*, SEL, ##__VA_ARGS__))objc_msgSendSuper)
|
||||
#define objc_msgSend_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))objc_msgSend)
|
||||
#define objc_msgSend_stret_t(RET, ...) ((RET(*)(id, SEL, ##__VA_ARGS__))abi_objc_msgSend_stret)
|
||||
#define objc_msgSend_id objc_msgSend_t(id)
|
||||
#define objc_msgSend_void objc_msgSend_t(void)
|
||||
#define objc_msgSend_void_id objc_msgSend_t(void, id)
|
||||
#define objc_msgSend_void_bool objc_msgSend_t(void, bool)
|
||||
|
||||
#define sel(NAME) sel_registerName(NAME)
|
||||
#define class(NAME) ((id)objc_getClass(NAME))
|
||||
#define makeClass(NAME, SUPER) \
|
||||
objc_allocateClassPair((Class)objc_getClass(SUPER), NAME, 0)
|
||||
|
||||
// Check here to get the signature right:
|
||||
// https://nshipster.com/type-encodings/
|
||||
// https://ko9.org/posts/encode-types/
|
||||
#define addMethod(CLASS, NAME, IMPL, SIGNATURE) \
|
||||
if (!class_addMethod(CLASS, sel(NAME), (IMP) (IMPL), (SIGNATURE))) assert(false)
|
||||
|
||||
#define addIvar(CLASS, NAME, SIZE, SIGNATURE) \
|
||||
if (!class_addIvar(CLASS, NAME, SIZE, rint(log2(SIZE)), SIGNATURE)) assert(false)
|
||||
|
||||
#define objc_alloc(CLASS) objc_msgSend_id(class(CLASS), sel("alloc"))
|
||||
#define autorelease(OBJ) objc_msgSend_void(OBJ, sel("autorelease"))
|
||||
#define retain(OBJ) objc_msgSend_void(OBJ, sel("retain"))
|
||||
#define release(OBJ) objc_msgSend_void(OBJ, sel("release"))
|
||||
|
||||
#if __LP64__ || NS_BUILD_32_LIKE_64
|
||||
#define NSIntegerEncoding "q"
|
||||
#define NSUIntegerEncoding "L"
|
||||
#else
|
||||
#define NSIntegerEncoding "i"
|
||||
#define NSUIntegerEncoding "I"
|
||||
#endif
|
||||
|
||||
#endif // defined(__IOS__) || defined (__MACOS__)
|
||||
#endif // NAETT_OBJC_H
|
||||
@@ -0,0 +1,200 @@
|
||||
#include "naett_internal.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#include "naett_objc.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
static void _showPools(const char* context) {
|
||||
fprintf(stderr, "NSAutoreleasePool@%s:\n", context);
|
||||
objc_msgSend_void(class("NSAutoreleasePool"), sel("showPools"));
|
||||
}
|
||||
#define showPools(x) _showPools((x))
|
||||
#else
|
||||
#define showPools(x)
|
||||
#endif
|
||||
|
||||
static id pool(void) {
|
||||
return objc_msgSend_id(objc_alloc("NSAutoreleasePool"), sel("init"));
|
||||
}
|
||||
|
||||
static id sessionConfiguration = nil;
|
||||
|
||||
void naettPlatformInit(naettInitData initData) {
|
||||
id NSThread = class("NSThread");
|
||||
SEL isMultiThreaded = sel("isMultiThreaded");
|
||||
|
||||
if (!objc_msgSend_t(bool)(NSThread, isMultiThreaded)) {
|
||||
// Make a dummy call from a new thread to kick Cocoa into multi-threaded mode
|
||||
objc_msgSend_t(void, SEL, id, id)(
|
||||
NSThread, sel("detachNewThreadSelector:toTarget:withObject:"), isMultiThreaded, NSThread, nil);
|
||||
}
|
||||
|
||||
sessionConfiguration = objc_msgSend_id(class("NSURLSessionConfiguration"), sel("ephemeralSessionConfiguration"));
|
||||
retain(sessionConfiguration);
|
||||
}
|
||||
|
||||
id NSString(const char* string) {
|
||||
return objc_msgSend_t(id, const char*)(class("NSString"), sel("stringWithUTF8String:"), string);
|
||||
}
|
||||
|
||||
int naettPlatformInitRequest(InternalRequest* req) {
|
||||
id p = pool();
|
||||
|
||||
id urlString = NSString(req->url);
|
||||
id url = objc_msgSend_t(id, id)(class("NSURL"), sel("URLWithString:"), urlString);
|
||||
|
||||
id request = objc_msgSend_t(id, id)(class("NSMutableURLRequest"), sel("requestWithURL:"), url);
|
||||
|
||||
objc_msgSend_t(void, double)(request, sel("setTimeoutInterval:"), (double)(req->options.timeoutMS) / 1000.0);
|
||||
id methodString = NSString(req->options.method);
|
||||
objc_msgSend_t(void, id)(request, sel("setHTTPMethod:"), methodString);
|
||||
|
||||
{
|
||||
id name = NSString("User-Agent");
|
||||
id value = NSString(req->options.userAgent ? req->options.userAgent : NAETT_UA);
|
||||
objc_msgSend_t(void, id, id)(request, sel("setValue:forHTTPHeaderField:"), value, name);
|
||||
}
|
||||
|
||||
KVLink* header = req->options.headers;
|
||||
while (header != NULL) {
|
||||
id name = NSString(header->key);
|
||||
id value = NSString(header->value);
|
||||
objc_msgSend_t(void, id, id)(request, sel("setValue:forHTTPHeaderField:"), value, name);
|
||||
header = header->next;
|
||||
}
|
||||
|
||||
char byteBuffer[10240];
|
||||
int bytesRead = 0;
|
||||
|
||||
if (req->options.bodyReader != NULL) {
|
||||
id bodyData =
|
||||
objc_msgSend_t(id, NSUInteger)(class("NSMutableData"), sel("dataWithCapacity:"), sizeof(byteBuffer));
|
||||
|
||||
int totalBytesRead = 0;
|
||||
do {
|
||||
bytesRead = req->options.bodyReader(byteBuffer, sizeof(byteBuffer), req->options.bodyReaderData);
|
||||
totalBytesRead += bytesRead;
|
||||
objc_msgSend_t(void, const void*, NSUInteger)(bodyData, sel("appendBytes:length:"), byteBuffer, bytesRead);
|
||||
} while (bytesRead > 0);
|
||||
|
||||
if (totalBytesRead > 0) {
|
||||
objc_msgSend_t(void, id)(request, sel("setHTTPBody:"), bodyData);
|
||||
}
|
||||
}
|
||||
|
||||
retain(request);
|
||||
req->urlRequest = request;
|
||||
|
||||
release(p);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void didReceiveData(id self, SEL _sel, id session, id dataTask, id data) {
|
||||
InternalResponse* res = NULL;
|
||||
id p = pool();
|
||||
|
||||
object_getInstanceVariable(self, "response", (void**)&res);
|
||||
|
||||
if (res->headers == NULL) {
|
||||
id response = objc_msgSend_t(id)(dataTask, sel("response"));
|
||||
res->code = objc_msgSend_t(NSInteger)(response, sel("statusCode"));
|
||||
id allHeaders = objc_msgSend_t(id)(response, sel("allHeaderFields"));
|
||||
|
||||
NSUInteger headerCount = objc_msgSend_t(NSUInteger)(allHeaders, sel("count"));
|
||||
id headerNames[headerCount];
|
||||
id headerValues[headerCount];
|
||||
|
||||
objc_msgSend_t(NSInteger, id*, id*, NSUInteger)(
|
||||
allHeaders, sel("getObjects:andKeys:count:"), headerValues, headerNames, headerCount);
|
||||
KVLink* firstHeader = NULL;
|
||||
for (int i = 0; i < headerCount; i++) {
|
||||
naettAlloc(KVLink, node);
|
||||
node->key = strdup(objc_msgSend_t(const char*)(headerNames[i], sel("UTF8String")));
|
||||
node->value = strdup(objc_msgSend_t(const char*)(headerValues[i], sel("UTF8String")));
|
||||
node->next = firstHeader;
|
||||
firstHeader = node;
|
||||
}
|
||||
res->headers = firstHeader;
|
||||
|
||||
const char* contentLength = naettGetHeader((naettRes*)res, "Content-Length");
|
||||
if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) {
|
||||
res->contentLength = -1;
|
||||
}
|
||||
}
|
||||
|
||||
const void* bytes = objc_msgSend_t(const void*)(data, sel("bytes"));
|
||||
NSUInteger length = objc_msgSend_t(NSUInteger)(data, sel("length"));
|
||||
|
||||
res->request->options.bodyWriter(bytes, length, res->request->options.bodyWriterData);
|
||||
res->totalBytesRead += (int)length;
|
||||
|
||||
release(p);
|
||||
}
|
||||
|
||||
static void didComplete(id self, SEL _sel, id session, id dataTask, id error) {
|
||||
InternalResponse* res = NULL;
|
||||
object_getInstanceVariable(self, "response", (void**)&res);
|
||||
if (res != NULL) {
|
||||
if (error != nil) {
|
||||
res->code = naettConnectionError;
|
||||
}
|
||||
res->complete = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static id createDelegate(void) {
|
||||
static Class TaskDelegateClass = nil;
|
||||
|
||||
if (!TaskDelegateClass) {
|
||||
TaskDelegateClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "naettTaskDelegate", 0);
|
||||
class_addProtocol(TaskDelegateClass, (Protocol* _Nonnull)objc_getProtocol("NSURLSessionDataDelegate"));
|
||||
|
||||
addMethod(TaskDelegateClass, "URLSession:dataTask:didReceiveData:", didReceiveData, "v@:@@@");
|
||||
addMethod(TaskDelegateClass, "URLSession:task:didCompleteWithError:", didComplete, "v@:@@@");
|
||||
addIvar(TaskDelegateClass, "response", sizeof(void*), "^v");
|
||||
}
|
||||
|
||||
id delegate = objc_msgSend_id((id)TaskDelegateClass, sel("alloc"));
|
||||
delegate = objc_msgSend_id(delegate, sel("init"));
|
||||
|
||||
return delegate;
|
||||
}
|
||||
|
||||
void naettPlatformMakeRequest(InternalResponse* res) {
|
||||
InternalRequest* req = res->request;
|
||||
id p = pool();
|
||||
|
||||
id delegate = createDelegate();
|
||||
// delegate will be retained by session below
|
||||
autorelease(delegate);
|
||||
object_setInstanceVariable(delegate, "response", (void*)res);
|
||||
|
||||
id session = objc_msgSend_t(id, id, id, id)(class("NSURLSession"),
|
||||
sel("sessionWithConfiguration:delegate:delegateQueue:"),
|
||||
sessionConfiguration,
|
||||
delegate,
|
||||
nil);
|
||||
|
||||
res->session = session;
|
||||
|
||||
id task = objc_msgSend_t(id, id)(session, sel("dataTaskWithRequest:"), req->urlRequest);
|
||||
objc_msgSend_void(task, sel("resume"));
|
||||
|
||||
release(p);
|
||||
}
|
||||
|
||||
void naettPlatformFreeRequest(InternalRequest* req) {
|
||||
release(req->urlRequest);
|
||||
req->urlRequest = nil;
|
||||
}
|
||||
|
||||
void naettPlatformCloseResponse(InternalResponse* res) {
|
||||
objc_msgSend_void(res->session, sel("invalidateAndCancel"));
|
||||
res->session = nil;
|
||||
}
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -0,0 +1,331 @@
|
||||
#include "naett_internal.h"
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <winhttp.h>
|
||||
#include <assert.h>
|
||||
#include <tchar.h>
|
||||
|
||||
void naettPlatformInit(naettInitData initData) {
|
||||
}
|
||||
|
||||
static char* winToUTF8(LPWSTR source) {
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, source, -1, NULL, 0, NULL, NULL);
|
||||
char* chars = (char*)malloc(length);
|
||||
int result = WideCharToMultiByte(CP_UTF8, 0, source, -1, chars, length, NULL, NULL);
|
||||
if (!result) {
|
||||
free(chars);
|
||||
return NULL;
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
static LPWSTR winFromUTF8(const char* source) {
|
||||
int length = MultiByteToWideChar(CP_UTF8, 0, source, -1, NULL, 0);
|
||||
LPWSTR chars = (LPWSTR)malloc(length * sizeof(WCHAR));
|
||||
int result = MultiByteToWideChar(CP_UTF8, 0, source, -1, chars, length);
|
||||
if (!result) {
|
||||
free(chars);
|
||||
return NULL;
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
#define ASPRINTF(result, fmt, ...) \
|
||||
{ \
|
||||
size_t len = snprintf(NULL, 0, fmt, __VA_ARGS__); \
|
||||
*(result) = (char*)malloc(len + 1); \
|
||||
snprintf(*(result), len + 1, fmt, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
static LPWSTR wcsndup(LPCWSTR str, size_t len) {
|
||||
LPWSTR result = calloc(1, sizeof(WCHAR) * (len + 1));
|
||||
wcsncpy(result, str, len);
|
||||
return result;
|
||||
}
|
||||
|
||||
static LPCWSTR packHeaders(InternalRequest* req) {
|
||||
char* packed = strdup("");
|
||||
|
||||
KVLink* node = req->options.headers;
|
||||
while (node != NULL) {
|
||||
char* update;
|
||||
ASPRINTF(&update, "%s%s:%s%s", packed, node->key, node->value, node->next ? "\r\n" : "");
|
||||
free(packed);
|
||||
packed = update;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
LPCWSTR winHeaders = winFromUTF8(packed);
|
||||
free(packed);
|
||||
return winHeaders;
|
||||
}
|
||||
|
||||
static void unpackHeaders(InternalResponse* res, LPWSTR packed) {
|
||||
size_t len = 0;
|
||||
KVLink* firstHeader = NULL;
|
||||
while ((len = wcslen(packed)) != 0) {
|
||||
char* header = winToUTF8(packed);
|
||||
char* split = strchr(header, ':');
|
||||
if (split) {
|
||||
*split = 0;
|
||||
split++;
|
||||
while (*split == ' ') {
|
||||
split++;
|
||||
}
|
||||
naettAlloc(KVLink, node);
|
||||
node->key = strdup(header);
|
||||
node->value = strdup(split);
|
||||
node->next = firstHeader;
|
||||
firstHeader = node;
|
||||
}
|
||||
free(header);
|
||||
packed += len + 1;
|
||||
}
|
||||
res->headers = firstHeader;
|
||||
}
|
||||
|
||||
static void CALLBACK
|
||||
callback(HINTERNET request, DWORD_PTR context, DWORD status, LPVOID statusInformation, DWORD statusInfoLength) {
|
||||
InternalResponse* res = (InternalResponse*)context;
|
||||
|
||||
switch (status) {
|
||||
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: {
|
||||
DWORD bufSize = 0;
|
||||
WinHttpQueryHeaders(request,
|
||||
WINHTTP_QUERY_RAW_HEADERS,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
NULL,
|
||||
&bufSize,
|
||||
WINHTTP_NO_HEADER_INDEX);
|
||||
LPWSTR buffer = (LPWSTR)malloc(bufSize);
|
||||
WinHttpQueryHeaders(request,
|
||||
WINHTTP_QUERY_RAW_HEADERS,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
buffer,
|
||||
&bufSize,
|
||||
WINHTTP_NO_HEADER_INDEX);
|
||||
unpackHeaders(res, buffer);
|
||||
free(buffer);
|
||||
|
||||
const char* contentLength = naettGetHeader((naettRes*)res, "Content-Length");
|
||||
if (!contentLength || sscanf(contentLength, "%d", &res->contentLength) != 1) {
|
||||
res->contentLength = -1;
|
||||
}
|
||||
|
||||
DWORD statusCode = 0;
|
||||
DWORD statusCodeSize = sizeof(statusCode);
|
||||
|
||||
WinHttpQueryHeaders(request,
|
||||
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
|
||||
WINHTTP_HEADER_NAME_BY_INDEX,
|
||||
&statusCode,
|
||||
&statusCodeSize,
|
||||
WINHTTP_NO_HEADER_INDEX);
|
||||
res->code = statusCode;
|
||||
|
||||
if (!WinHttpQueryDataAvailable(request, NULL)) {
|
||||
res->code = naettProtocolError;
|
||||
res->complete = 1;
|
||||
}
|
||||
} break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: {
|
||||
DWORD* available = (DWORD*)statusInformation;
|
||||
res->bytesLeft = *available;
|
||||
if (res->bytesLeft == 0) {
|
||||
res->complete = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
size_t bytesToRead = min(res->bytesLeft, sizeof(res->buffer));
|
||||
if (!WinHttpReadData(request, res->buffer, (DWORD)bytesToRead, NULL)) {
|
||||
res->code = naettReadError;
|
||||
res->complete = 1;
|
||||
}
|
||||
} break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: {
|
||||
size_t bytesRead = statusInfoLength;
|
||||
|
||||
InternalRequest* req = res->request;
|
||||
if (req->options.bodyWriter(res->buffer, (int)bytesRead, req->options.bodyWriterData) != bytesRead) {
|
||||
res->code = naettReadError;
|
||||
res->complete = 1;
|
||||
}
|
||||
res->totalBytesRead += (int)bytesRead;
|
||||
res->bytesLeft -= bytesRead;
|
||||
if (res->bytesLeft > 0) {
|
||||
size_t bytesToRead = min(res->bytesLeft, sizeof(res->buffer));
|
||||
if (!WinHttpReadData(request, res->buffer, (DWORD)bytesToRead, NULL)) {
|
||||
res->code = naettReadError;
|
||||
res->complete = 1;
|
||||
}
|
||||
} else {
|
||||
if (!WinHttpQueryDataAvailable(request, NULL)) {
|
||||
res->code = naettProtocolError;
|
||||
res->complete = 1;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE:
|
||||
case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: {
|
||||
int bytesRead = res->request->options.bodyReader(
|
||||
res->buffer, sizeof(res->buffer), res->request->options.bodyReaderData);
|
||||
if (bytesRead) {
|
||||
WinHttpWriteData(request, res->buffer, bytesRead, NULL);
|
||||
} else {
|
||||
if (!WinHttpReceiveResponse(request, NULL)) {
|
||||
res->code = naettReadError;
|
||||
res->complete = 1;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
//
|
||||
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: {
|
||||
WINHTTP_ASYNC_RESULT* result = (WINHTTP_ASYNC_RESULT*)statusInformation;
|
||||
switch (result->dwResult) {
|
||||
case API_RECEIVE_RESPONSE:
|
||||
case API_QUERY_DATA_AVAILABLE:
|
||||
case API_READ_DATA:
|
||||
res->code = naettReadError;
|
||||
break;
|
||||
case API_WRITE_DATA:
|
||||
res->code = naettWriteError;
|
||||
break;
|
||||
case API_SEND_REQUEST:
|
||||
res->code = naettConnectionError;
|
||||
break;
|
||||
default:
|
||||
res->code = naettGenericError;
|
||||
}
|
||||
|
||||
res->complete = 1;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
int naettPlatformInitRequest(InternalRequest* req) {
|
||||
LPWSTR url = winFromUTF8(req->url);
|
||||
|
||||
URL_COMPONENTS components;
|
||||
ZeroMemory(&components, sizeof(components));
|
||||
components.dwStructSize = sizeof(components);
|
||||
components.dwSchemeLength = (DWORD)-1;
|
||||
components.dwHostNameLength = (DWORD)-1;
|
||||
components.dwUrlPathLength = (DWORD)-1;
|
||||
components.dwExtraInfoLength = (DWORD)-1;
|
||||
BOOL cracked = WinHttpCrackUrl(url, 0, 0, &components);
|
||||
|
||||
if (!cracked) {
|
||||
free(url);
|
||||
return 0;
|
||||
}
|
||||
|
||||
req->host = wcsndup(components.lpszHostName, components.dwHostNameLength);
|
||||
req->resource = wcsndup(components.lpszUrlPath, components.dwUrlPathLength + components.dwExtraInfoLength);
|
||||
free(url);
|
||||
|
||||
LPWSTR uaBuf = winFromUTF8(req->options.userAgent ? req->options.userAgent : NAETT_UA);
|
||||
req->session = WinHttpOpen(uaBuf,
|
||||
WINHTTP_ACCESS_TYPE_NO_PROXY,
|
||||
WINHTTP_NO_PROXY_NAME,
|
||||
WINHTTP_NO_PROXY_BYPASS,
|
||||
WINHTTP_FLAG_ASYNC);
|
||||
free(uaBuf);
|
||||
|
||||
if (!req->session) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
WinHttpSetStatusCallback(req->session, callback, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, 0);
|
||||
|
||||
// Set the connect timeout. Leave the other three timeouts at their default values.
|
||||
WinHttpSetTimeouts(req->session, 0, req->options.timeoutMS, 30000, 30000);
|
||||
|
||||
req->connection = WinHttpConnect(req->session, req->host, components.nPort, 0);
|
||||
if (!req->connection) {
|
||||
naettPlatformFreeRequest(req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LPWSTR verb = winFromUTF8(req->options.method);
|
||||
req->request = WinHttpOpenRequest(req->connection,
|
||||
verb,
|
||||
req->resource,
|
||||
NULL,
|
||||
WINHTTP_NO_REFERER,
|
||||
WINHTTP_DEFAULT_ACCEPT_TYPES,
|
||||
components.nScheme == INTERNET_SCHEME_HTTPS ? WINHTTP_FLAG_SECURE : 0);
|
||||
free(verb);
|
||||
if (!req->request) {
|
||||
naettPlatformFreeRequest(req);
|
||||
return 0;
|
||||
}
|
||||
|
||||
LPCWSTR headers = packHeaders(req);
|
||||
if (headers[0] != 0) {
|
||||
if (!WinHttpAddRequestHeaders(
|
||||
req->request, headers, -1, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) {
|
||||
naettPlatformFreeRequest(req);
|
||||
free((LPWSTR)headers);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
free((LPWSTR)headers);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void naettPlatformMakeRequest(InternalResponse* res) {
|
||||
InternalRequest* req = res->request;
|
||||
|
||||
LPCWSTR extraHeaders = WINHTTP_NO_ADDITIONAL_HEADERS;
|
||||
WCHAR contentLengthHeader[64];
|
||||
|
||||
int contentLength = req->options.bodyReader(NULL, 0, req->options.bodyReaderData);
|
||||
if (contentLength > 0) {
|
||||
swprintf(contentLengthHeader, 64, L"Content-Length: %d", contentLength);
|
||||
extraHeaders = contentLengthHeader;
|
||||
}
|
||||
|
||||
if (!WinHttpSendRequest(req->request, extraHeaders, -1, NULL, 0, 0, (DWORD_PTR)res)) {
|
||||
res->code = naettConnectionError;
|
||||
res->complete = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void naettPlatformFreeRequest(InternalRequest* req) {
|
||||
assert(req != NULL);
|
||||
|
||||
if (req->request != NULL) {
|
||||
WinHttpCloseHandle(req->request);
|
||||
req->request = NULL;
|
||||
}
|
||||
if (req->connection != NULL) {
|
||||
WinHttpCloseHandle(req->connection);
|
||||
req->connection = NULL;
|
||||
}
|
||||
if (req->session != NULL) {
|
||||
WinHttpCloseHandle(req->session);
|
||||
req->session = NULL;
|
||||
}
|
||||
if (req->host != NULL) {
|
||||
free(req->host);
|
||||
req->host = NULL;
|
||||
}
|
||||
if (req->resource != NULL) {
|
||||
free(req->resource);
|
||||
req->resource = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void naettPlatformCloseResponse(InternalResponse* res) {
|
||||
}
|
||||
|
||||
#endif // __WINDOWS__
|
||||
Binary file not shown.
Reference in new issue
Block a user