diff --git a/.gitignore b/.gitignore index 7cb4fe57f0..a83f49e72b 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,7 @@ wiiu/wut/elf2rpl/elf2rpl pkg/apple/iOS/build/ pkg/apple/iOS/modules/ +pkg/apple/tvOS/modules/ pkg/apple/build/ ui/drivers/qt/moc_* ui/drivers/moc_* diff --git a/audio/drivers/coreaudio.c b/audio/drivers/coreaudio.c index 30d6f7c1ca..f35fa96e77 100644 --- a/audio/drivers/coreaudio.c +++ b/audio/drivers/coreaudio.c @@ -125,7 +125,9 @@ static OSStatus audio_write_cb(void *userdata, static void coreaudio_interrupt_listener(void *data, UInt32 interrupt_state) { (void)data; +#if TARGET_OS_IOS g_interrupted = (interrupt_state == kAudioSessionBeginInterruption); +#endif } #else static void choose_output_device(coreaudio_t *dev, const char* device) @@ -218,7 +220,7 @@ static void *coreaudio_init(const char *device, dev->lock = slock_new(); dev->cond = scond_new(); -#if TARGET_OS_IPHONE +#if TARGET_OS_IOS if (!session_initialized) { session_initialized = true; diff --git a/frontend/drivers/platform_darwin.m b/frontend/drivers/platform_darwin.m index d7b654349a..c3996548d0 100644 --- a/frontend/drivers/platform_darwin.m +++ b/frontend/drivers/platform_darwin.m @@ -115,7 +115,11 @@ static NSSearchPathDirectory NSConvertFlagsCF(unsigned flags) switch (flags) { case CFDocumentDirectory: +#if TARGET_OS_IOS return NSDocumentDirectory; +#elif TARGET_OS_TV + return NSCachesDirectory; +#endif } return 0; @@ -363,7 +367,7 @@ static void frontend_darwin_get_environment_settings(int *argc, char *argv[], home_dir_buf, "shaders_glsl", sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER])); #endif -#if TARGET_OS_IPHONE +#if TARGET_OS_IOS int major, minor; get_ios_version(&major, &minor); if (major >= 10 ) @@ -372,6 +376,16 @@ static void frontend_darwin_get_environment_settings(int *argc, char *argv[], else fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], home_dir_buf, "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE])); +#elif TARGET_OS_TV + printf("tvOS bundle path = %s",bundle_path_buf); + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], + bundle_path_buf, "modules", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE])); + NSError *fileError; + NSArray *bundleFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[NSString stringWithUTF8String:g_defaults.dirs[DEFAULT_DIR_CORE]] error:&fileError]; + NSLog(@"tvOS Files in modules:"); + for (NSString *f in bundleFiles) { + NSLog(@"%@",f); + } #else fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], home_dir_buf, "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE])); #endif @@ -423,7 +437,7 @@ static void frontend_darwin_get_environment_settings(int *argc, char *argv[], #endif #endif -#if TARGET_OS_IPHONE +#if TARGET_OS_IOS char assets_zip_path[PATH_MAX_LENGTH]; if (major > 8) strlcpy(g_defaults.path.buildbot_server_url, "http://buildbot.libretro.com/nightly/apple/ios9/latest/", sizeof(g_defaults.path.buildbot_server_url)); @@ -582,7 +596,7 @@ static enum frontend_powerstate frontend_darwin_get_powerstate(int *seconds, int end: if (blob) CFRelease(blob); -#elif defined(IOS) +#elif TARGET_OS_IOS float level; UIDevice *uidev = [UIDevice currentDevice]; diff --git a/gfx/drivers_context/cocoa_gl_ctx.m b/gfx/drivers_context/cocoa_gl_ctx.m index f9201f2fff..18649c2b34 100644 --- a/gfx/drivers_context/cocoa_gl_ctx.m +++ b/gfx/drivers_context/cocoa_gl_ctx.m @@ -133,12 +133,16 @@ static void glkitview_init_xibs(void) void *glkitview_init(void) { #if defined(HAVE_COCOATOUCH) +#if TARGET_OS_IOS glkitview_init_xibs(); +#endif g_view = [GLKView new]; +#if TARGET_OS_IOS g_view.multipleTouchEnabled = YES; + [g_view addSubview:g_pause_indicator_view]; +#endif g_view.enableSetNeedsDisplay = NO; - [g_view addSubview:g_pause_indicator_view]; return (BRIDGE void *)((GLKView*)g_view); #else diff --git a/griffin/griffin_objc.m b/griffin/griffin_objc.m index f11512e7a4..da5dfec212 100644 --- a/griffin/griffin_objc.m +++ b/griffin/griffin_objc.m @@ -38,11 +38,11 @@ #if defined(HAVE_COCOATOUCH) -#if TARGET_OS_IPHONE +#if TARGET_OS_IOS #include "../ui/drivers/cocoa/cocoatouch_menu.m" +#endif #include "../ui/drivers/ui_cocoatouch.m" -#endif #elif defined(HAVE_COCOA) #include "../ui/drivers/ui_cocoa.m" diff --git a/input/drivers_joypad/hid_joypad.c b/input/drivers_joypad/hid_joypad.c index c35aff850d..df58a4de41 100644 --- a/input/drivers_joypad/hid_joypad.c +++ b/input/drivers_joypad/hid_joypad.c @@ -19,8 +19,19 @@ static const hid_driver_t *generic_hid = NULL; +static void hid_joypad_autodetect_add(unsigned autoconf_pad) +{ +#ifdef TARGET_OS_TV + // AppleTV: mfi controllers are HID joypad - add an autodetect here + if ( !input_autoconfigure_connect("Mfi Controller", NULL, hid_joypad.ident, autoconf_pad, 0, 0)) { + input_config_set_device_name(autoconf_pad, "Mfi Controller"); + } +#endif +} + static bool hid_joypad_init(void *data) { + hid_joypad_autodetect_add(0); generic_hid = input_hid_init_first(); if (!generic_hid) return false; diff --git a/input/drivers_joypad/mfi_joypad.m b/input/drivers_joypad/mfi_joypad.m index 32f52e334c..75caae9478 100644 --- a/input/drivers_joypad/mfi_joypad.m +++ b/input/drivers_joypad/mfi_joypad.m @@ -22,7 +22,7 @@ #include #include -#import "../include/GameController/GameController.h" +#import #ifndef MAX_MFI_CONTROLLERS #define MAX_MFI_CONTROLLERS 4 @@ -33,6 +33,8 @@ static int16_t mfi_axes[MAX_USERS][4]; static uint32_t mfi_controllers[MAX_MFI_CONTROLLERS]; +static NSMutableArray *mfiControllers; + enum { GCCONTROLLER_PLAYER_INDEX_UNSET = -1, @@ -162,6 +164,28 @@ static void apple_gamecontroller_joypad_connect(GCController *controller) } } + [mfiControllers addObject:controller]; + // move any non-game controllers (like the siri remote) to the end + if ( mfiControllers.count > 1 ) { + NSInteger connectedNonGameControllerIndex = NSNotFound; + NSUInteger index = 0; + for (GCController *connectedController in mfiControllers) { + if ( connectedController.gamepad == nil && connectedController.extendedGamepad == nil ) { + connectedNonGameControllerIndex = index; + } + index++; + } + if ( connectedNonGameControllerIndex != NSNotFound ) { + GCController *nonGameController = [mfiControllers objectAtIndex:connectedNonGameControllerIndex]; + [mfiControllers removeObjectAtIndex:connectedNonGameControllerIndex]; + [mfiControllers addObject:nonGameController]; + } + int newPlayerIndex = 0; + for (GCController *gc in mfiControllers) { + gc.playerIndex = newPlayerIndex++; + } + } + apple_gamecontroller_joypad_register(controller.gamepad); } } @@ -174,6 +198,7 @@ static void apple_gamecontroller_joypad_disconnect(GCController* controller) return; mfi_controllers[pad] = 0; + [mfiControllers removeObject:controller]; } bool apple_gamecontroller_joypad_init(void *data) @@ -183,7 +208,7 @@ bool apple_gamecontroller_joypad_init(void *data) return true; if (!apple_gamecontroller_available()) return false; - + mfiControllers = [[NSMutableArray alloc] initWithCapacity:MAX_MFI_CONTROLLERS]; #ifdef __IPHONE_7_0 [[NSNotificationCenter defaultCenter] addObserverForName:GCControllerDidConnectNotification object:nil diff --git a/input/input_autodetect_builtin.c b/input/input_autodetect_builtin.c index 7cda7ca0e8..03483e252c 100644 --- a/input/input_autodetect_builtin.c +++ b/input/input_autodetect_builtin.c @@ -630,6 +630,28 @@ DECL_AXIS(r_x_minus, -2) \ DECL_AXIS(r_y_plus, +3) \ DECL_AXIS(r_y_minus, -3) +#define TVOS_MFI_DEFAULT_BINDS \ +DECL_BTN(a, 8) \ +DECL_BTN(b, 0) \ +DECL_BTN(x, 9) \ +DECL_BTN(y, 1) \ +DECL_BTN(up, 4) \ +DECL_BTN(down, 5) \ +DECL_BTN(left, 6) \ +DECL_BTN(right, 7) \ +DECL_BTN(l, 10) \ +DECL_BTN(r, 11) \ +DECL_AXIS(l2, 12) \ +DECL_AXIS(r2, 13) \ +DECL_AXIS(l_x_plus, +0) \ +DECL_AXIS(l_x_minus, -0) \ +DECL_AXIS(l_y_plus, -1) \ +DECL_AXIS(l_y_minus, +1) \ +DECL_AXIS(r_x_plus, +2) \ +DECL_AXIS(r_x_minus, -2) \ +DECL_AXIS(r_y_plus, -3) \ +DECL_AXIS(r_y_minus, +3) + const char* const input_builtin_autoconfs[] = { #if defined(_WIN32) && defined(_XBOX) @@ -704,6 +726,9 @@ const char* const input_builtin_autoconfs[] = #endif #ifdef EMSCRIPTEN DECL_AUTOCONF_PID(1, 1, "rwebpad", EMSCRIPTEN_DEFAULT_BINDS), +#endif +#ifdef TARGET_OS_TV + DECL_AUTOCONF_DEVICE("Mfi Controller", "hid", TVOS_MFI_DEFAULT_BINDS), #endif NULL }; diff --git a/pkg/apple/Resources/modules/fceumm_libretro_tvos.dylib b/pkg/apple/Resources/modules/fceumm_libretro_tvos.dylib new file mode 100755 index 0000000000..53f308f07b Binary files /dev/null and b/pkg/apple/Resources/modules/fceumm_libretro_tvos.dylib differ diff --git a/pkg/apple/RetroArch_iOS11.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_iOS11.xcodeproj/project.pbxproj index c8a7fa4b1a..f4966b053b 100644 --- a/pkg/apple/RetroArch_iOS11.xcodeproj/project.pbxproj +++ b/pkg/apple/RetroArch_iOS11.xcodeproj/project.pbxproj @@ -26,17 +26,55 @@ 9204BE201D319EF300BD49DB /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96AFAE3116C1D4EA009DE44C /* OpenGLES.framework */; }; 9204BE221D319EF300BD49DB /* iOS/Resources/ic_pause.png in Resources */ = {isa = PBXBuildFile; fileRef = 83D632DB19ECFCC4009E3161 /* iOS/Resources/ic_pause.png */; }; 9204BE231D319EF300BD49DB /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 967894611788EBD800D6CA69 /* InfoPlist.strings */; }; - 9204BE241D319EF300BD49DB /* iOS/Resources/Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69D31DE31A547EC800EF4C92 /* iOS/Resources/Media.xcassets */; }; 9204BE251D319EF300BD49DB /* iOS/Resources/PauseIndicatorView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 83D632DE19ECFCC4009E3161 /* iOS/Resources/PauseIndicatorView.xib */; }; 9204BE261D319EF300BD49DB /* iOS/modules in Resources */ = {isa = PBXBuildFile; fileRef = 83EB675F19EEAF050096F441 /* iOS/modules */; }; + 926C77E321FD1E6700103EDE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 926C77E221FD1E6700103EDE /* Assets.xcassets */; }; + 926C77EA21FD20C100103EDE /* griffin_objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 50521A431AA23BF500185CC9 /* griffin_objc.m */; }; + 926C77EB21FD20C400103EDE /* griffin.c in Sources */ = {isa = PBXBuildFile; fileRef = 501232C9192E5FC40063A359 /* griffin.c */; }; + 926C77EF21FD263800103EDE /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 926C77EE21FD263800103EDE /* AudioToolbox.framework */; }; + 926C77F121FD26E800103EDE /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 926C77F021FD26E800103EDE /* GameController.framework */; }; + 9297844F2200227700989A60 /* iOS/Resources/Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69D31DE31A547EC800EF4C92 /* iOS/Resources/Media.xcassets */; }; + 92CC05A221FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058221FE3C1700FF79F0 /* GCDWebServerResponse.m */; }; + 92CC05A321FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058221FE3C1700FF79F0 /* GCDWebServerResponse.m */; }; + 92CC05A421FE3C1700FF79F0 /* GCDWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058521FE3C1700FF79F0 /* GCDWebServerRequest.m */; }; + 92CC05A521FE3C1700FF79F0 /* GCDWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058521FE3C1700FF79F0 /* GCDWebServerRequest.m */; }; + 92CC05A621FE3C1700FF79F0 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058821FE3C1700FF79F0 /* GCDWebServerFunctions.m */; }; + 92CC05A721FE3C1700FF79F0 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058821FE3C1700FF79F0 /* GCDWebServerFunctions.m */; }; + 92CC05A821FE3C1700FF79F0 /* GCDWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058921FE3C1700FF79F0 /* GCDWebServer.m */; }; + 92CC05A921FE3C1700FF79F0 /* GCDWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058921FE3C1700FF79F0 /* GCDWebServer.m */; }; + 92CC05AA21FE3C1700FF79F0 /* GCDWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058A21FE3C1700FF79F0 /* GCDWebServerConnection.m */; }; + 92CC05AB21FE3C1700FF79F0 /* GCDWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058A21FE3C1700FF79F0 /* GCDWebServerConnection.m */; }; + 92CC05AC21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058F21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m */; }; + 92CC05AD21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC058F21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m */; }; + 92CC05AE21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059121FE3C1700FF79F0 /* GCDWebServerFileResponse.m */; }; + 92CC05AF21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059121FE3C1700FF79F0 /* GCDWebServerFileResponse.m */; }; + 92CC05B021FE3C1700FF79F0 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059221FE3C1700FF79F0 /* GCDWebServerDataResponse.m */; }; + 92CC05B121FE3C1700FF79F0 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059221FE3C1700FF79F0 /* GCDWebServerDataResponse.m */; }; + 92CC05B221FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059421FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m */; }; + 92CC05B321FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059421FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m */; }; + 92CC05B421FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059921FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m */; }; + 92CC05B521FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059921FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m */; }; + 92CC05B621FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059A21FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m */; }; + 92CC05B721FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059A21FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m */; }; + 92CC05B821FE3C1700FF79F0 /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059B21FE3C1700FF79F0 /* GCDWebServerDataRequest.m */; }; + 92CC05B921FE3C1700FF79F0 /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059B21FE3C1700FF79F0 /* GCDWebServerDataRequest.m */; }; + 92CC05BA21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059C21FE3C1700FF79F0 /* GCDWebServerFileRequest.m */; }; + 92CC05BB21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC059C21FE3C1700FF79F0 /* GCDWebServerFileRequest.m */; }; + 92CC05BC21FE3C1700FF79F0 /* GCDWebUploader.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 92CC059F21FE3C1700FF79F0 /* GCDWebUploader.bundle */; }; + 92CC05BD21FE3C1700FF79F0 /* GCDWebUploader.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 92CC059F21FE3C1700FF79F0 /* GCDWebUploader.bundle */; }; + 92CC05BE21FE3C1700FF79F0 /* GCDWebUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05A121FE3C1700FF79F0 /* GCDWebUploader.m */; }; + 92CC05BF21FE3C1700FF79F0 /* GCDWebUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05A121FE3C1700FF79F0 /* GCDWebUploader.m */; }; + 92CC05C221FE3C6D00FF79F0 /* WebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05C121FE3C6D00FF79F0 /* WebServer.m */; }; + 92CC05C321FE3C6D00FF79F0 /* WebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 92CC05C121FE3C6D00FF79F0 /* WebServer.m */; }; + 92CC05C521FEDC9F00FF79F0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92CC05C421FEDC9F00FF79F0 /* CFNetwork.framework */; }; + 92CC05C721FEDD0B00FF79F0 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92CC05C621FEDD0B00FF79F0 /* MobileCoreServices.framework */; }; + 92CC05CE21FF798F00FF79F0 /* modules in Resources */ = {isa = PBXBuildFile; fileRef = 92CC05CD21FF798F00FF79F0 /* modules */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 0FDA2A921BE1AFA800F2B5DA /* RetroArch_iOS9-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "RetroArch_iOS9-Info.plist"; path = "/Users/buildbot/buildbot/ios/retroarch/pkg/apple/RetroArch_iOS9-Info.plist"; sourceTree = ""; }; 501232C9192E5FC40063A359 /* griffin.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = griffin.c; path = ../../griffin/griffin.c; sourceTree = SOURCE_ROOT; }; 501881EB184BAD6D006F665D /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; 501881ED184BB54C006F665D /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; - 503700AE1ACA18E400A51A37 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = iOS/Info.plist; sourceTree = SOURCE_ROOT; }; 5040F04F1AE47ED4006F6972 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; 50521A431AA23BF500185CC9 /* griffin_objc.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = griffin_objc.m; path = ../../griffin/griffin_objc.m; sourceTree = SOURCE_ROOT; }; 50C3B1AD1AB1107100F478D3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; @@ -48,6 +86,48 @@ 83D632DE19ECFCC4009E3161 /* iOS/Resources/PauseIndicatorView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = iOS/Resources/PauseIndicatorView.xib; sourceTree = SOURCE_ROOT; }; 83EB675F19EEAF050096F441 /* iOS/modules */ = {isa = PBXFileReference; lastKnownFileType = folder; path = iOS/modules; sourceTree = SOURCE_ROOT; }; 9204BE2B1D319EF300BD49DB /* RetroArchiOS11.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RetroArchiOS11.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 926C77D721FD1E6500103EDE /* RetroArchTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RetroArchTV.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 926C77E221FD1E6700103EDE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 926C77E421FD1E6700103EDE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 926C77EC21FD261600103EDE /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.1.sdk/System/Library/Frameworks/CoreAudio.framework; sourceTree = DEVELOPER_DIR; }; + 926C77EE21FD263800103EDE /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.1.sdk/System/Library/Frameworks/AudioToolbox.framework; sourceTree = DEVELOPER_DIR; }; + 926C77F021FD26E800103EDE /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.1.sdk/System/Library/Frameworks/GameController.framework; sourceTree = DEVELOPER_DIR; }; + 92CC058021FE3C1700FF79F0 /* GCDWebServerFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFunctions.h; sourceTree = ""; }; + 92CC058121FE3C1700FF79F0 /* GCDWebServerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerPrivate.h; sourceTree = ""; }; + 92CC058221FE3C1700FF79F0 /* GCDWebServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerResponse.m; sourceTree = ""; }; + 92CC058321FE3C1700FF79F0 /* GCDWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerConnection.h; sourceTree = ""; }; + 92CC058421FE3C1700FF79F0 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = ""; }; + 92CC058521FE3C1700FF79F0 /* GCDWebServerRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerRequest.m; sourceTree = ""; }; + 92CC058621FE3C1700FF79F0 /* GCDWebServerHTTPStatusCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerHTTPStatusCodes.h; sourceTree = ""; }; + 92CC058721FE3C1700FF79F0 /* GCDWebServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerResponse.h; sourceTree = ""; }; + 92CC058821FE3C1700FF79F0 /* GCDWebServerFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFunctions.m; sourceTree = ""; }; + 92CC058921FE3C1700FF79F0 /* GCDWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServer.m; sourceTree = ""; }; + 92CC058A21FE3C1700FF79F0 /* GCDWebServerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerConnection.m; sourceTree = ""; }; + 92CC058B21FE3C1700FF79F0 /* GCDWebServerRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerRequest.h; sourceTree = ""; }; + 92CC058D21FE3C1700FF79F0 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = ""; }; + 92CC058E21FE3C1700FF79F0 /* GCDWebServerStreamedResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerStreamedResponse.h; sourceTree = ""; }; + 92CC058F21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerErrorResponse.m; sourceTree = ""; }; + 92CC059021FE3C1700FF79F0 /* GCDWebServerDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataResponse.h; sourceTree = ""; }; + 92CC059121FE3C1700FF79F0 /* GCDWebServerFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileResponse.m; sourceTree = ""; }; + 92CC059221FE3C1700FF79F0 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataResponse.m; sourceTree = ""; }; + 92CC059321FE3C1700FF79F0 /* GCDWebServerErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerErrorResponse.h; sourceTree = ""; }; + 92CC059421FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerStreamedResponse.m; sourceTree = ""; }; + 92CC059621FE3C1700FF79F0 /* GCDWebServerDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataRequest.h; sourceTree = ""; }; + 92CC059721FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerMultiPartFormRequest.h; sourceTree = ""; }; + 92CC059821FE3C1700FF79F0 /* GCDWebServerFileRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileRequest.h; sourceTree = ""; }; + 92CC059921FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerURLEncodedFormRequest.m; sourceTree = ""; }; + 92CC059A21FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerMultiPartFormRequest.m; sourceTree = ""; }; + 92CC059B21FE3C1700FF79F0 /* GCDWebServerDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataRequest.m; sourceTree = ""; }; + 92CC059C21FE3C1700FF79F0 /* GCDWebServerFileRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileRequest.m; sourceTree = ""; }; + 92CC059D21FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerURLEncodedFormRequest.h; sourceTree = ""; }; + 92CC059F21FE3C1700FF79F0 /* GCDWebUploader.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = GCDWebUploader.bundle; sourceTree = ""; }; + 92CC05A021FE3C1700FF79F0 /* GCDWebUploader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebUploader.h; sourceTree = ""; }; + 92CC05A121FE3C1700FF79F0 /* GCDWebUploader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebUploader.m; sourceTree = ""; }; + 92CC05C021FE3C6D00FF79F0 /* WebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebServer.h; sourceTree = ""; }; + 92CC05C121FE3C6D00FF79F0 /* WebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WebServer.m; sourceTree = ""; }; + 92CC05C421FEDC9F00FF79F0 /* CFNetwork.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CFNetwork.framework; path = System/Library/Frameworks/CFNetwork.framework; sourceTree = SDKROOT; }; + 92CC05C621FEDD0B00FF79F0 /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; }; + 92CC05CD21FF798F00FF79F0 /* modules */ = {isa = PBXFileReference; lastKnownFileType = folder; path = modules; sourceTree = ""; }; 96366C5416C9AC3300D64A22 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; 96366C5816C9ACF500D64A22 /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; 963C3C33186E3DED00A6EB1E /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; }; @@ -58,8 +138,6 @@ 96AFAE2D16C1D4EA009DE44C /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 96AFAE2F16C1D4EA009DE44C /* GLKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GLKit.framework; path = System/Library/Frameworks/GLKit.framework; sourceTree = SDKROOT; }; 96AFAE3116C1D4EA009DE44C /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; - 96AFAF4516C1E00A009DE44C /* bitmap.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = bitmap.bin; sourceTree = ""; }; - 96AFAF4616C1E00A009DE44C /* bitmap.bmp */ = {isa = PBXFileReference; lastKnownFileType = image.bmp; path = bitmap.bmp; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -67,6 +145,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92CC05C721FEDD0B00FF79F0 /* MobileCoreServices.framework in Frameworks */, + 92CC05C521FEDC9F00FF79F0 /* CFNetwork.framework in Frameworks */, 9204BE121D319EF300BD49DB /* libz.dylib in Frameworks */, 9204BE131D319EF300BD49DB /* QuartzCore.framework in Frameworks */, 9204BE141D319EF300BD49DB /* GameController.framework in Frameworks */, @@ -85,10 +165,19 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 926C77D421FD1E6500103EDE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 926C77F121FD26E800103EDE /* GameController.framework in Frameworks */, + 926C77EF21FD263800103EDE /* AudioToolbox.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 83D632D719ECFCC4009E3161 /* Assets */ = { + 83D632D719ECFCC4009E3161 /* iOS */ = { isa = PBXGroup; children = ( 83EB675F19EEAF050096F441 /* iOS/modules */, @@ -96,20 +185,110 @@ 83D632DE19ECFCC4009E3161 /* iOS/Resources/PauseIndicatorView.xib */, 69D31DE31A547EC800EF4C92 /* iOS/Resources/Media.xcassets */, ); - name = Assets; + name = iOS; path = Resources; sourceTree = ""; }; + 926C77D821FD1E6500103EDE /* tvOS */ = { + isa = PBXGroup; + children = ( + 92CC05CD21FF798F00FF79F0 /* modules */, + 926C77E221FD1E6700103EDE /* Assets.xcassets */, + 926C77E421FD1E6700103EDE /* Info.plist */, + ); + path = tvOS; + sourceTree = ""; + }; + 92CC057E21FE3C1700FF79F0 /* GCDWebServer */ = { + isa = PBXGroup; + children = ( + 92CC057F21FE3C1700FF79F0 /* Core */, + 92CC058C21FE3C1700FF79F0 /* Responses */, + 92CC059521FE3C1700FF79F0 /* Requests */, + ); + path = GCDWebServer; + sourceTree = ""; + }; + 92CC057F21FE3C1700FF79F0 /* Core */ = { + isa = PBXGroup; + children = ( + 92CC058021FE3C1700FF79F0 /* GCDWebServerFunctions.h */, + 92CC058121FE3C1700FF79F0 /* GCDWebServerPrivate.h */, + 92CC058221FE3C1700FF79F0 /* GCDWebServerResponse.m */, + 92CC058321FE3C1700FF79F0 /* GCDWebServerConnection.h */, + 92CC058421FE3C1700FF79F0 /* GCDWebServer.h */, + 92CC058521FE3C1700FF79F0 /* GCDWebServerRequest.m */, + 92CC058621FE3C1700FF79F0 /* GCDWebServerHTTPStatusCodes.h */, + 92CC058721FE3C1700FF79F0 /* GCDWebServerResponse.h */, + 92CC058821FE3C1700FF79F0 /* GCDWebServerFunctions.m */, + 92CC058921FE3C1700FF79F0 /* GCDWebServer.m */, + 92CC058A21FE3C1700FF79F0 /* GCDWebServerConnection.m */, + 92CC058B21FE3C1700FF79F0 /* GCDWebServerRequest.h */, + ); + path = Core; + sourceTree = ""; + }; + 92CC058C21FE3C1700FF79F0 /* Responses */ = { + isa = PBXGroup; + children = ( + 92CC058D21FE3C1700FF79F0 /* GCDWebServerFileResponse.h */, + 92CC058E21FE3C1700FF79F0 /* GCDWebServerStreamedResponse.h */, + 92CC058F21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m */, + 92CC059021FE3C1700FF79F0 /* GCDWebServerDataResponse.h */, + 92CC059121FE3C1700FF79F0 /* GCDWebServerFileResponse.m */, + 92CC059221FE3C1700FF79F0 /* GCDWebServerDataResponse.m */, + 92CC059321FE3C1700FF79F0 /* GCDWebServerErrorResponse.h */, + 92CC059421FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m */, + ); + path = Responses; + sourceTree = ""; + }; + 92CC059521FE3C1700FF79F0 /* Requests */ = { + isa = PBXGroup; + children = ( + 92CC059621FE3C1700FF79F0 /* GCDWebServerDataRequest.h */, + 92CC059721FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.h */, + 92CC059821FE3C1700FF79F0 /* GCDWebServerFileRequest.h */, + 92CC059921FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m */, + 92CC059A21FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m */, + 92CC059B21FE3C1700FF79F0 /* GCDWebServerDataRequest.m */, + 92CC059C21FE3C1700FF79F0 /* GCDWebServerFileRequest.m */, + 92CC059D21FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.h */, + ); + path = Requests; + sourceTree = ""; + }; + 92CC059E21FE3C1700FF79F0 /* GCDWebUploader */ = { + isa = PBXGroup; + children = ( + 92CC059F21FE3C1700FF79F0 /* GCDWebUploader.bundle */, + 92CC05A021FE3C1700FF79F0 /* GCDWebUploader.h */, + 92CC05A121FE3C1700FF79F0 /* GCDWebUploader.m */, + ); + path = GCDWebUploader; + sourceTree = ""; + }; + 92CC05CC21FF782C00FF79F0 /* WebServer */ = { + isa = PBXGroup; + children = ( + 92CC05C021FE3C6D00FF79F0 /* WebServer.h */, + 92CC05C121FE3C6D00FF79F0 /* WebServer.m */, + 92CC057E21FE3C1700FF79F0 /* GCDWebServer */, + 92CC059E21FE3C1700FF79F0 /* GCDWebUploader */, + ); + path = WebServer; + sourceTree = ""; + }; 96AFAE1A16C1D4EA009DE44C = { isa = PBXGroup; children = ( - 83D632D719ECFCC4009E3161 /* Assets */, 96AFAE9C16C1D976009DE44C /* core */, + 83D632D719ECFCC4009E3161 /* iOS */, + 926C77D821FD1E6500103EDE /* tvOS */, + 92CC05CC21FF782C00FF79F0 /* WebServer */, 96AFAE2816C1D4EA009DE44C /* Frameworks */, 96AFAE2616C1D4EA009DE44C /* Products */, 96AFAE3416C1D4EA009DE44C /* Supporting Files */, - 503700AE1ACA18E400A51A37 /* Info.plist */, - 0FDA2A921BE1AFA800F2B5DA /* RetroArch_iOS9-Info.plist */, ); indentWidth = 3; sourceTree = ""; @@ -119,6 +298,7 @@ isa = PBXGroup; children = ( 9204BE2B1D319EF300BD49DB /* RetroArchiOS11.app */, + 926C77D721FD1E6500103EDE /* RetroArchTV.app */, ); name = Products; sourceTree = ""; @@ -126,6 +306,11 @@ 96AFAE2816C1D4EA009DE44C /* Frameworks */ = { isa = PBXGroup; children = ( + 92CC05C621FEDD0B00FF79F0 /* MobileCoreServices.framework */, + 92CC05C421FEDC9F00FF79F0 /* CFNetwork.framework */, + 926C77F021FD26E800103EDE /* GameController.framework */, + 926C77EE21FD263800103EDE /* AudioToolbox.framework */, + 926C77EC21FD261600103EDE /* CoreAudio.framework */, 5040F04F1AE47ED4006F6972 /* libz.dylib */, 50C3B1AD1AB1107100F478D3 /* QuartzCore.framework */, 696012F119F3389A006A1088 /* CoreText.framework */, @@ -152,36 +337,16 @@ 9678945F1788EBD000D6CA69 /* Info.plist */, ); name = "Supporting Files"; - path = RetroArch; sourceTree = ""; }; 96AFAE9C16C1D976009DE44C /* core */ = { isa = PBXGroup; children = ( D48581DC16F823E2004BEB17 /* griffin */, - 96AFAF3116C1E00A009DE44C /* gfx */, ); name = core; sourceTree = ""; }; - 96AFAF3116C1E00A009DE44C /* gfx */ = { - isa = PBXGroup; - children = ( - 96AFAF4416C1E00A009DE44C /* fonts */, - ); - name = gfx; - path = ../gfx; - sourceTree = ""; - }; - 96AFAF4416C1E00A009DE44C /* fonts */ = { - isa = PBXGroup; - children = ( - 96AFAF4516C1E00A009DE44C /* bitmap.bin */, - 96AFAF4616C1E00A009DE44C /* bitmap.bmp */, - ); - path = fonts; - sourceTree = ""; - }; D48581DC16F823E2004BEB17 /* griffin */ = { isa = PBXGroup; children = ( @@ -212,6 +377,24 @@ productReference = 9204BE2B1D319EF300BD49DB /* RetroArchiOS11.app */; productType = "com.apple.product-type.application"; }; + 926C77D621FD1E6500103EDE /* RetroArchTV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 926C77E921FD1E6700103EDE /* Build configuration list for PBXNativeTarget "RetroArchTV" */; + buildPhases = ( + 926C77D321FD1E6500103EDE /* Sources */, + 926C77D421FD1E6500103EDE /* Frameworks */, + 92CC057521FE2D4900FF79F0 /* ShellScript */, + 926C77D521FD1E6500103EDE /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RetroArchTV; + productName = RetroArchTV; + productReference = 926C77D721FD1E6500103EDE /* RetroArchTV.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -222,8 +405,13 @@ ORGANIZATIONNAME = RetroArch; TargetAttributes = { 9204BE091D319EF300BD49DB = { - DevelopmentTeam = UK699V5ZS8; - DevelopmentTeamName = "RetroArch"; + DevelopmentTeam = R72X3BF4KE; + DevelopmentTeamName = RetroArch; + }; + 926C77D621FD1E6500103EDE = { + CreatedOnToolsVersion = 10.1; + DevelopmentTeam = R72X3BF4KE; + ProvisioningStyle = Automatic; }; }; }; @@ -233,6 +421,7 @@ hasScannedForEncodings = 0; knownRegions = ( en, + Base, ); mainGroup = 96AFAE1A16C1D4EA009DE44C; productRefGroup = 96AFAE2616C1D4EA009DE44C /* Products */; @@ -240,6 +429,7 @@ projectRoot = ""; targets = ( 9204BE091D319EF300BD49DB /* RetroArchiOS11 */, + 926C77D621FD1E6500103EDE /* RetroArchTV */, ); }; /* End PBXProject section */ @@ -251,12 +441,23 @@ files = ( 9204BE221D319EF300BD49DB /* iOS/Resources/ic_pause.png in Resources */, 9204BE231D319EF300BD49DB /* InfoPlist.strings in Resources */, - 9204BE241D319EF300BD49DB /* iOS/Resources/Media.xcassets in Resources */, + 92CC05BC21FE3C1700FF79F0 /* GCDWebUploader.bundle in Resources */, 9204BE251D319EF300BD49DB /* iOS/Resources/PauseIndicatorView.xib in Resources */, 9204BE261D319EF300BD49DB /* iOS/modules in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 926C77D521FD1E6500103EDE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 9297844F2200227700989A60 /* iOS/Resources/Media.xcassets in Resources */, + 92CC05CE21FF798F00FF79F0 /* modules in Resources */, + 92CC05BD21FE3C1700FF79F0 /* GCDWebUploader.bundle in Resources */, + 926C77E321FD1E6700103EDE /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -271,7 +472,24 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "./code-sign-cores.sh"; + shellScript = "./code-sign-cores.sh iOS\n"; + }; + 92CC057521FE2D4900FF79F0 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "./code-sign-cores.sh tvOS\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -280,8 +498,47 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 92CC05B821FE3C1700FF79F0 /* GCDWebServerDataRequest.m in Sources */, + 92CC05A421FE3C1700FF79F0 /* GCDWebServerRequest.m in Sources */, + 92CC05AA21FE3C1700FF79F0 /* GCDWebServerConnection.m in Sources */, + 92CC05BE21FE3C1700FF79F0 /* GCDWebUploader.m in Sources */, + 92CC05B621FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m in Sources */, + 92CC05AC21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m in Sources */, + 92CC05A621FE3C1700FF79F0 /* GCDWebServerFunctions.m in Sources */, + 92CC05A821FE3C1700FF79F0 /* GCDWebServer.m in Sources */, 9204BE0D1D319EF300BD49DB /* griffin_objc.m in Sources */, 9204BE101D319EF300BD49DB /* griffin.c in Sources */, + 92CC05B421FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m in Sources */, + 92CC05B021FE3C1700FF79F0 /* GCDWebServerDataResponse.m in Sources */, + 92CC05C221FE3C6D00FF79F0 /* WebServer.m in Sources */, + 92CC05BA21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */, + 92CC05AE21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */, + 92CC05B221FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */, + 92CC05A221FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 926C77D321FD1E6500103EDE /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 92CC05B921FE3C1700FF79F0 /* GCDWebServerDataRequest.m in Sources */, + 92CC05A521FE3C1700FF79F0 /* GCDWebServerRequest.m in Sources */, + 92CC05AB21FE3C1700FF79F0 /* GCDWebServerConnection.m in Sources */, + 92CC05BF21FE3C1700FF79F0 /* GCDWebUploader.m in Sources */, + 92CC05B721FE3C1700FF79F0 /* GCDWebServerMultiPartFormRequest.m in Sources */, + 92CC05AD21FE3C1700FF79F0 /* GCDWebServerErrorResponse.m in Sources */, + 92CC05A721FE3C1700FF79F0 /* GCDWebServerFunctions.m in Sources */, + 92CC05A921FE3C1700FF79F0 /* GCDWebServer.m in Sources */, + 926C77EA21FD20C100103EDE /* griffin_objc.m in Sources */, + 926C77EB21FD20C400103EDE /* griffin.c in Sources */, + 92CC05B521FE3C1700FF79F0 /* GCDWebServerURLEncodedFormRequest.m in Sources */, + 92CC05B121FE3C1700FF79F0 /* GCDWebServerDataResponse.m in Sources */, + 92CC05C321FE3C6D00FF79F0 /* WebServer.m in Sources */, + 92CC05BB21FE3C1700FF79F0 /* GCDWebServerFileRequest.m in Sources */, + 92CC05AF21FE3C1700FF79F0 /* GCDWebServerFileResponse.m in Sources */, + 92CC05B321FE3C1700FF79F0 /* GCDWebServerStreamedResponse.m in Sources */, + 92CC05A321FE3C1700FF79F0 /* GCDWebServerResponse.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -309,7 +566,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_RESOURCE_RULES_PATH = "$(SDKROOT)/ResourceRules.plist"; - DEVELOPMENT_TEAM = UK699V5ZS8; + DEVELOPMENT_TEAM = R72X3BF4KE; ENABLE_BITCODE = NO; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_PREFIX_HEADER = ""; @@ -334,7 +591,7 @@ "-DHAVE_NETWORKING", "-DHAVE_NETPLAYDISCOVERY", "-DHAVE_AVFOUNDATION", - "-DHAVE_RUNAHEAD", + "-DHAVE_RUNAHEAD", "-DHAVE_GRIFFIN", "-DHAVE_STB_VORBIS", "-DHAVE_MINIUPNPC", @@ -380,7 +637,7 @@ "-DHAVE_AVFOUNDATION", "-DHAVE_KEYMAPPER", ); - PRODUCT_BUNDLE_IDENTIFIER = com.libretro.RetroArchiOS11; + PRODUCT_BUNDLE_IDENTIFIER = com.libretro.RetroArchiOS11T; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; VALID_ARCHS = "armv7 arm64"; @@ -399,7 +656,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_RESOURCE_RULES_PATH = "$(SDKROOT)/ResourceRules.plist"; - DEVELOPMENT_TEAM = UK699V5ZS8; + DEVELOPMENT_TEAM = R72X3BF4KE; ENABLE_BITCODE = NO; GCC_PRECOMPILE_PREFIX_HEADER = NO; GCC_PREFIX_HEADER = ""; @@ -414,63 +671,6 @@ IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_NO_PIE = YES; LIBRARY_SEARCH_PATHS = ""; - OTHER_CFLAGS = ( - "-DNS_BLOCK_ASSERTIONS=1", - "-DNDEBUG", - "-DDONT_WANT_ARM_OPTIMIZATIONS", - "-DHAVE_NETWORKGAMEPAD", - "-DHAVE_CORETEXT", - "-DHAVE_HID", - "-DHAVE_NETWORKING", - "-DHAVE_NETPLAYDISCOVERY", - "-DHAVE_AVFOUNDATION", - "-DHAVE_RUNAHEAD", - "-DHAVE_GRIFFIN", - "-DHAVE_STB_VORBIS", - "-DHAVE_MINIUPNPC", - "-DHAVE_BUILTINMINIUPNPC", - "-DHAVE_UPDATE_ASSETS", - "-DHAVE_LANGEXTRA", - "-DHAVE_CHEEVOS", - "-DRC_DISABLE_LUA", - "-DHAVE_IMAGEVIEWER", - "-DHAVE_CORELOCATION", - "-DHAVE_RGUI", - "-DHAVE_MENU", - "-DHAVE_LIBRETRODB", - "-DIOS", - "-DHAVE_OPENGL", - "-DHAVE_OPENGLES", - "-DHAVE_OPENGLES2", - "-DHAVE_CC_RESAMPLER", - "-DHAVE_GLSL", - "-DINLINE=inline", - "-D__LIBRETRO__", - "-DRARCH_MOBILE", - "-DHAVE_COREAUDIO", - "-DHAVE_DYNAMIC", - "-DHAVE_OVERLAY", - "-DHAVE_ZLIB", - "-DHAVE_RPNG", - "-DHAVE_RJPEG", - "-DHAVE_RBMP", - "-DHAVE_RTGA", - "-DHAVE_COCOATOUCH", - "-DHAVE_MAIN", - "-DRARCH_INTERNAL", - "-DHAVE_THREADS", - "-DHAVE_FILTERS_BUILTIN", - "-DHAVE_7ZIP", - "-DHAVE_MATERIALUI", - "-DHAVE_XMB", - "-DHAVE_OZONE", - "-DHAVE_SHADERPIPELINE", - "-D_LZMA_UINT32_IS_ULONG", - "-DHAVE_MFI", - "-DHAVE_BTSTACK", - "-DHAVE_AVFOUNDATION", - "-DHAVE_KEYMAPPER", - ); "OTHER_CFLAGS[arch=*]" = ( "-DNS_BLOCK_ASSERTIONS=1", "-DNDEBUG", @@ -481,7 +681,7 @@ "-DHAVE_NETWORKING", "-DHAVE_NETPLAYDISCOVERY", "-DHAVE_AVFOUNDATION", - "-DHAVE_RUNAHEAD", + "-DHAVE_RUNAHEAD", "-DHAVE_GRIFFIN", "-DHAVE_STB_VORBIS", "-DHAVE_MINIUPNPC", @@ -528,7 +728,7 @@ "-DHAVE_AVFOUNDATION", "-DHAVE_KEYMAPPER", ); - PRODUCT_BUNDLE_IDENTIFIER = com.libretro.RetroArchiOS11; + PRODUCT_BUNDLE_IDENTIFIER = com.libretro.RetroArchiOS11T; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; VALID_ARCHS = "armv7 arm64"; @@ -537,6 +737,235 @@ }; name = Release; }; + 926C77E721FD1E6700103EDE /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = ""; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + HEADER_SEARCH_PATHS = ( + ../../, + "../../libretro-common/include", + ../../deps/stb, + ../../deps/rcheevos/include, + ../../deps/libz, + ../../deps, + ); + INFOPLIST_FILE = "$(SRCROOT)/tvOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = ( + "-DDONT_WANT_ARM_OPTIMIZATIONS", + "-DHAVE_NETWORKGAMEPAD", + "-DHAVE_CORETEXT", + "-DHAVE_HID", + "-DHAVE_NETWORKING", + "-DHAVE_NETPLAYDISCOVERY", + "-DHAVE_RUNAHEAD", + "-DHAVE_GRIFFIN", + "-DHAVE_STB_VORBIS", + "-DHAVE_MINIUPNPC", + "-DHAVE_BUILTINMINIUPNPC", + "-DHAVE_UPDATE_ASSETS", + "-DHAVE_LANGEXTRA", + "-DHAVE_CHEEVOS", + "-DRC_DISABLE_LUA", + "-DHAVE_IMAGEVIEWER", + "-DHAVE_RGUI", + "-DHAVE_MENU", + "-DHAVE_LIBRETRODB", + "-DIOS", + "-DHAVE_OPENGL", + "-DHAVE_OPENGLES", + "-DHAVE_OPENGLES2", + "-DHAVE_CC_RESAMPLER", + "-DHAVE_GLSL", + "-DINLINE=inline", + "-D__LIBRETRO__", + "-DRARCH_MOBILE", + "-DHAVE_COREAUDIO", + "-DHAVE_DYNAMIC", + "-DHAVE_OVERLAY", + "-DHAVE_ZLIB", + "-DHAVE_RPNG", + "-DHAVE_RJPEG", + "-DHAVE_RBMP", + "-DHAVE_RTGA", + "-DHAVE_COCOATOUCH", + "-DHAVE_MAIN", + "-DRARCH_INTERNAL", + "-DHAVE_THREADS", + "-DHAVE_FILTERS_BUILTIN", + "-DHAVE_XMB", + "-DHAVE_OZONE", + "-DHAVE_SHADERPIPELINE", + "-D_LZMA_UINT32_IS_ULONG", + "-DHAVE_MFI", + "-DHAVE_BTSTACK", + "-DHAVE_KEYMAPPER", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.libretro.RetroArchTV; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Debug; + }; + 926C77E821FD1E6700103EDE /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + HEADER_SEARCH_PATHS = ( + ../../, + "../../libretro-common/include", + ../../deps/stb, + ../../deps/libz, + ../../deps, + ); + INFOPLIST_FILE = "$(SRCROOT)/tvOS/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + OTHER_CFLAGS = ( + "-DNS_BLOCK_ASSERTIONS=1", + "-DNDEBUG", + "-DDONT_WANT_ARM_OPTIMIZATIONS", + "-DHAVE_NETWORKGAMEPAD", + "-DHAVE_CORETEXT", + "-DHAVE_HID", + "-DHAVE_NETWORKING", + "-DHAVE_NETPLAYDISCOVERY", + "-DHAVE_RUNAHEAD", + "-DHAVE_GRIFFIN", + "-DHAVE_STB_VORBIS", + "-DHAVE_MINIUPNPC", + "-DHAVE_BUILTINMINIUPNPC", + "-DHAVE_UPDATE_ASSETS", + "-DHAVE_LANGEXTRA", + "-DHAVE_CHEEVOS", + "-DRC_DISABLE_LUA", + "-DHAVE_IMAGEVIEWER", + "-DHAVE_RGUI", + "-DHAVE_MENU", + "-DHAVE_LIBRETRODB", + "-DIOS", + "-DHAVE_OPENGL", + "-DHAVE_OPENGLES", + "-DHAVE_OPENGLES2", + "-DHAVE_CC_RESAMPLER", + "-DHAVE_GLSL", + "-DINLINE=inline", + "-D__LIBRETRO__", + "-DRARCH_MOBILE", + "-DHAVE_COREAUDIO", + "-DHAVE_DYNAMIC", + "-DHAVE_OVERLAY", + "-DHAVE_ZLIB", + "-DHAVE_RPNG", + "-DHAVE_RJPEG", + "-DHAVE_RBMP", + "-DHAVE_RTGA", + "-DHAVE_COCOATOUCH", + "-DHAVE_MAIN", + "-DRARCH_INTERNAL", + "-DHAVE_THREADS", + "-DHAVE_FILTERS_BUILTIN", + "-DHAVE_7ZIP", + "-DHAVE_XMB", + "-DHAVE_OZONE", + "-DHAVE_SHADERPIPELINE", + "-D_LZMA_UINT32_IS_ULONG", + "-DHAVE_MFI", + "-DHAVE_BTSTACK", + "-DHAVE_KEYMAPPER", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.libretro.RetroArchTV; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SDKROOT = appletvos; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 12.1; + }; + name = Release; + }; 96AFAE5216C1D4EA009DE44C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -572,7 +1001,7 @@ "-DHAVE_NETWORKING", "-DHAVE_NETPLAYDISCOVERY", "-DHAVE_AVFOUNDATION", - "-DHAVE_RUNAHEAD", + "-DHAVE_RUNAHEAD", "-DHAVE_GRIFFIN", "-DHAVE_STB_VORBIS", "-DHAVE_MINIUPNPC", @@ -645,7 +1074,7 @@ "-DHAVE_NETWORKING", "-DHAVE_NETPLAYDISCOVERY", "-DHAVE_AVFOUNDATION", - "-DHAVE_RUNAHEAD", + "-DHAVE_RUNAHEAD", "-DHAVE_GRIFFIN", "-DHAVE_STB_VORBIS", "-DHAVE_MINIUPNPC", @@ -701,6 +1130,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 926C77E921FD1E6700103EDE /* Build configuration list for PBXNativeTarget "RetroArchTV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 926C77E721FD1E6700103EDE /* Debug */, + 926C77E821FD1E6700103EDE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 96AFAE1F16C1D4EA009DE44C /* Build configuration list for PBXProject "RetroArch_iOS11" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/pkg/apple/RetroArch_iOS11.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/pkg/apple/RetroArch_iOS11.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/pkg/apple/RetroArch_iOS11.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/pkg/apple/RetroArch_iOS11.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/pkg/apple/RetroArch_iOS11.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..0c67376eba --- /dev/null +++ b/pkg/apple/RetroArch_iOS11.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,5 @@ + + + + + diff --git a/pkg/apple/RetroArch_iOS11.xcodeproj/xcshareddata/xcschemes/RetroArch iOS.xcscheme b/pkg/apple/RetroArch_iOS11.xcodeproj/xcshareddata/xcschemes/RetroArch iOS.xcscheme new file mode 100644 index 0000000000..63abbb2ea4 --- /dev/null +++ b/pkg/apple/RetroArch_iOS11.xcodeproj/xcshareddata/xcschemes/RetroArch iOS.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkg/apple/RetroArch_iOS11.xcodeproj/xcshareddata/xcschemes/RetroArch tvOS.xcscheme b/pkg/apple/RetroArch_iOS11.xcodeproj/xcshareddata/xcschemes/RetroArch tvOS.xcscheme new file mode 100644 index 0000000000..487d4ec288 --- /dev/null +++ b/pkg/apple/RetroArch_iOS11.xcodeproj/xcshareddata/xcschemes/RetroArch tvOS.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServer.h b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServer.h new file mode 100644 index 0000000000..473e21fa93 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServer.h @@ -0,0 +1,622 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import + +#import "GCDWebServerRequest.h" +#import "GCDWebServerResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerMatchBlock is called for every handler added to the + * GCDWebServer whenever a new HTTP request has started (i.e. HTTP headers have + * been received). The block is passed the basic info for the request (HTTP method, + * URL, headers...) and must decide if it wants to handle it or not. + * + * If the handler can handle the request, the block must return a new + * GCDWebServerRequest instance created with the same basic info. + * Otherwise, it simply returns nil. + */ +typedef GCDWebServerRequest* _Nullable (^GCDWebServerMatchBlock)(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery); + +/** + * The GCDWebServerProcessBlock is called after the HTTP request has been fully + * received (i.e. the entire HTTP body has been read). The block is passed the + * GCDWebServerRequest created at the previous step by the GCDWebServerMatchBlock. + * + * The block must return a GCDWebServerResponse or nil on error, which will + * result in a 500 HTTP status code returned to the client. It's however + * recommended to return a GCDWebServerErrorResponse on error so more useful + * information can be returned to the client. + */ +typedef GCDWebServerResponse* _Nullable (^GCDWebServerProcessBlock)(__kindof GCDWebServerRequest* request); + +/** + * The GCDWebServerAsynchronousProcessBlock works like the GCDWebServerProcessBlock + * except the GCDWebServerResponse can be returned to the server at a later time + * allowing for asynchronous generation of the response. + * + * The block must eventually call "completionBlock" passing a GCDWebServerResponse + * or nil on error, which will result in a 500 HTTP status code returned to the client. + * It's however recommended to return a GCDWebServerErrorResponse on error so more + * useful information can be returned to the client. + */ +typedef void (^GCDWebServerCompletionBlock)(GCDWebServerResponse* _Nullable response); +typedef void (^GCDWebServerAsyncProcessBlock)(__kindof GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock); + +/** + * The port used by the GCDWebServer (NSNumber / NSUInteger). + * + * The default value is 0 i.e. let the OS pick a random port. + */ +extern NSString* const GCDWebServerOption_Port; + +/** + * The Bonjour name used by the GCDWebServer (NSString). If set to an empty string, + * the name will automatically take the value of the GCDWebServerOption_ServerName + * option. If this option is set to nil, Bonjour will be disabled. + * + * The default value is nil. + */ +extern NSString* const GCDWebServerOption_BonjourName; + +/** + * The Bonjour service type used by the GCDWebServer (NSString). + * + * The default value is "_http._tcp", the service type for HTTP web servers. + */ +extern NSString* const GCDWebServerOption_BonjourType; + +/** + * Request a port mapping in the NAT gateway (NSNumber / BOOL). + * + * This uses the DNSService API under the hood which supports IPv4 mappings only. + * + * The default value is NO. + * + * @warning The external port set up by the NAT gateway may be different than + * the one used by the GCDWebServer. + */ +extern NSString* const GCDWebServerOption_RequestNATPortMapping; + +/** + * Only accept HTTP requests coming from localhost i.e. not from the outside + * network (NSNumber / BOOL). + * + * The default value is NO. + * + * @warning Bonjour and NAT port mapping should be disabled if using this option + * since the server will not be reachable from the outside network anyway. + */ +extern NSString* const GCDWebServerOption_BindToLocalhost; + +/** + * The maximum number of incoming HTTP requests that can be queued waiting to + * be handled before new ones are dropped (NSNumber / NSUInteger). + * + * The default value is 16. + */ +extern NSString* const GCDWebServerOption_MaxPendingConnections; + +/** + * The value for "Server" HTTP header used by the GCDWebServer (NSString). + * + * The default value is the GCDWebServer class name. + */ +extern NSString* const GCDWebServerOption_ServerName; + +/** + * The authentication method used by the GCDWebServer + * (one of "GCDWebServerAuthenticationMethod_..."). + * + * The default value is nil i.e. authentication is disabled. + */ +extern NSString* const GCDWebServerOption_AuthenticationMethod; + +/** + * The authentication realm used by the GCDWebServer (NSString). + * + * The default value is the same as the GCDWebServerOption_ServerName option. + */ +extern NSString* const GCDWebServerOption_AuthenticationRealm; + +/** + * The authentication accounts used by the GCDWebServer + * (NSDictionary of username / password pairs). + * + * The default value is nil i.e. no accounts. + */ +extern NSString* const GCDWebServerOption_AuthenticationAccounts; + +/** + * The class used by the GCDWebServer when instantiating GCDWebServerConnection + * (subclass of GCDWebServerConnection). + * + * The default value is the GCDWebServerConnection class. + */ +extern NSString* const GCDWebServerOption_ConnectionClass; + +/** + * Allow the GCDWebServer to pretend "HEAD" requests are actually "GET" ones + * and automatically discard the HTTP body of the response (NSNumber / BOOL). + * + * The default value is YES. + */ +extern NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET; + +/** + * The interval expressed in seconds used by the GCDWebServer to decide how to + * coalesce calls to -webServerDidConnect: and -webServerDidDisconnect: + * (NSNumber / double). Coalescing will be disabled if the interval is <= 0.0. + * + * The default value is 1.0 second. + */ +extern NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval; + +/** + * Set the dispatch queue priority on which server connection will be + * run (NSNumber / long). + * + * + * The default value is DISPATCH_QUEUE_PRIORITY_DEFAULT. + */ +extern NSString* const GCDWebServerOption_DispatchQueuePriority; + +#if TARGET_OS_IPHONE + +/** + * Enables the GCDWebServer to automatically suspend itself (as if -stop was + * called) when the iOS app goes into the background and the last + * GCDWebServerConnection is closed, then resume itself (as if -start was called) + * when the iOS app comes back to the foreground (NSNumber / BOOL). + * + * See the README.md file for more information about this option. + * + * The default value is YES. + * + * @warning The running property will be NO while the GCDWebServer is suspended. + */ +extern NSString* const GCDWebServerOption_AutomaticallySuspendInBackground; + +#endif + +/** + * HTTP Basic Authentication scheme (see https://tools.ietf.org/html/rfc2617). + * + * @warning Use of this authentication scheme is not recommended as the + * passwords are sent in clear. + */ +extern NSString* const GCDWebServerAuthenticationMethod_Basic; + +/** + * HTTP Digest Access Authentication scheme (see https://tools.ietf.org/html/rfc2617). + */ +extern NSString* const GCDWebServerAuthenticationMethod_DigestAccess; + +@class GCDWebServer; + +/** + * Delegate methods for GCDWebServer. + * + * @warning These methods are always called on the main thread in a serialized way. + */ +@protocol GCDWebServerDelegate +@optional + +/** + * This method is called after the server has successfully started. + */ +- (void)webServerDidStart:(GCDWebServer*)server; + +/** + * This method is called after the Bonjour registration for the server has + * successfully completed. + * + * Use the "bonjourServerURL" property to retrieve the Bonjour address of the + * server. + */ +- (void)webServerDidCompleteBonjourRegistration:(GCDWebServer*)server; + +/** + * This method is called after the NAT port mapping for the server has been + * updated. + * + * Use the "publicServerURL" property to retrieve the public address of the + * server. + */ +- (void)webServerDidUpdateNATPortMapping:(GCDWebServer*)server; + +/** + * This method is called when the first GCDWebServerConnection is opened by the + * server to serve a series of HTTP requests. + * + * A series of HTTP requests is considered ongoing as long as new HTTP requests + * keep coming (and new GCDWebServerConnection instances keep being opened), + * until before the last HTTP request has been responded to (and the + * corresponding last GCDWebServerConnection closed). + */ +- (void)webServerDidConnect:(GCDWebServer*)server; + +/** + * This method is called when the last GCDWebServerConnection is closed after + * the server has served a series of HTTP requests. + * + * The GCDWebServerOption_ConnectedStateCoalescingInterval option can be used + * to have the server wait some extra delay before considering that the series + * of HTTP requests has ended (in case there some latency between consecutive + * requests). This effectively coalesces the calls to -webServerDidConnect: + * and -webServerDidDisconnect:. + */ +- (void)webServerDidDisconnect:(GCDWebServer*)server; + +/** + * This method is called after the server has stopped. + */ +- (void)webServerDidStop:(GCDWebServer*)server; + +@end + +/** + * The GCDWebServer class listens for incoming HTTP requests on a given port, + * then passes each one to a "handler" capable of generating an HTTP response + * for it, which is then sent back to the client. + * + * GCDWebServer instances can be created and used from any thread but it's + * recommended to have the main thread's runloop be running so internal callbacks + * can be handled e.g. for Bonjour registration. + * + * See the README.md file for more information about the architecture of GCDWebServer. + */ +@interface GCDWebServer : NSObject + +/** + * Sets the delegate for the server. + */ +@property(nonatomic, weak, nullable) id delegate; + +/** + * Returns YES if the server is currently running. + */ +@property(nonatomic, readonly, getter=isRunning) BOOL running; + +/** + * Returns the port used by the server. + * + * @warning This property is only valid if the server is running. + */ +@property(nonatomic, readonly) NSUInteger port; + +/** + * Returns the Bonjour name used by the server. + * + * @warning This property is only valid if the server is running and Bonjour + * registration has successfully completed, which can take up to a few seconds. + */ +@property(nonatomic, readonly, nullable) NSString* bonjourName; + +/** + * Returns the Bonjour service type used by the server. + * + * @warning This property is only valid if the server is running and Bonjour + * registration has successfully completed, which can take up to a few seconds. + */ +@property(nonatomic, readonly, nullable) NSString* bonjourType; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)init; + +/** + * Adds to the server a handler that generates responses synchronously when handling incoming HTTP requests. + * + * Handlers are called in a LIFO queue, so if multiple handlers can potentially + * respond to a given request, the latest added one wins. + * + * @warning Addling handlers while the server is running is not allowed. + */ +- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock; + +/** + * Adds to the server a handler that generates responses asynchronously when handling incoming HTTP requests. + * + * Handlers are called in a LIFO queue, so if multiple handlers can potentially + * respond to a given request, the latest added one wins. + * + * @warning Addling handlers while the server is running is not allowed. + */ +- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock; + +/** + * Removes all handlers previously added to the server. + * + * @warning Removing handlers while the server is running is not allowed. + */ +- (void)removeAllHandlers; + +/** + * Starts the server with explicit options. This method is the designated way + * to start the server. + * + * Returns NO if the server failed to start and sets "error" argument if not NULL. + */ +- (BOOL)startWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error; + +/** + * Stops the server and prevents it to accepts new HTTP requests. + * + * @warning Stopping the server does not abort GCDWebServerConnection instances + * currently handling already received HTTP requests. These connections will + * continue to execute normally until completion. + */ +- (void)stop; + +@end + +@interface GCDWebServer (Extensions) + +/** + * Returns the server's URL. + * + * @warning This property is only valid if the server is running. + */ +@property(nonatomic, readonly, nullable) NSURL* serverURL; + +/** + * Returns the server's Bonjour URL. + * + * @warning This property is only valid if the server is running and Bonjour + * registration has successfully completed, which can take up to a few seconds. + * Also be aware this property will not automatically update if the Bonjour hostname + * has been dynamically changed after the server started running (this should be rare). + */ +@property(nonatomic, readonly, nullable) NSURL* bonjourServerURL; + +/** + * Returns the server's public URL. + * + * @warning This property is only valid if the server is running and NAT port + * mapping is active. + */ +@property(nonatomic, readonly, nullable) NSURL* publicServerURL; + +/** + * Starts the server on port 8080 (OS X & iOS Simulator) or port 80 (iOS) + * using the default Bonjour name. + * + * Returns NO if the server failed to start. + */ +- (BOOL)start; + +/** + * Starts the server on a given port and with a specific Bonjour name. + * Pass a nil Bonjour name to disable Bonjour entirely or an empty string to + * use the default name. + * + * Returns NO if the server failed to start. + */ +- (BOOL)startWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name; + +#if !TARGET_OS_IPHONE + +/** + * Runs the server synchronously using -startWithPort:bonjourName: until a + * SIGINT signal is received i.e. Ctrl-C. This method is intended to be used + * by command line tools. + * + * Returns NO if the server failed to start. + * + * @warning This method must be used from the main thread only. + */ +- (BOOL)runWithPort:(NSUInteger)port bonjourName:(nullable NSString*)name; + +/** + * Runs the server synchronously using -startWithOptions: until a SIGTERM or + * SIGINT signal is received i.e. Ctrl-C in Terminal. This method is intended to + * be used by command line tools. + * + * Returns NO if the server failed to start and sets "error" argument if not NULL. + * + * @warning This method must be used from the main thread only. + */ +- (BOOL)runWithOptions:(nullable NSDictionary*)options error:(NSError** _Nullable)error; + +#endif + +@end + +@interface GCDWebServer (Handlers) + +/** + * Adds a default handler to the server to handle all incoming HTTP requests + * with a given HTTP method and generate responses synchronously. + */ +- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; + +/** + * Adds a default handler to the server to handle all incoming HTTP requests + * with a given HTTP method and generate responses asynchronously. + */ +- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block; + +/** + * Adds a handler to the server to handle incoming HTTP requests with a given + * HTTP method and a specific case-insensitive path and generate responses + * synchronously. + */ +- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; + +/** + * Adds a handler to the server to handle incoming HTTP requests with a given + * HTTP method and a specific case-insensitive path and generate responses + * asynchronously. + */ +- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block; + +/** + * Adds a handler to the server to handle incoming HTTP requests with a given + * HTTP method and a path matching a case-insensitive regular expression and + * generate responses synchronously. + */ +- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block; + +/** + * Adds a handler to the server to handle incoming HTTP requests with a given + * HTTP method and a path matching a case-insensitive regular expression and + * generate responses asynchronously. + */ +- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block; + +@end + +@interface GCDWebServer (GETHandlers) + +/** + * Adds a handler to the server to respond to incoming "GET" HTTP requests + * with a specific case-insensitive path with in-memory data. + */ +- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(nullable NSString*)contentType cacheAge:(NSUInteger)cacheAge; + +/** + * Adds a handler to the server to respond to incoming "GET" HTTP requests + * with a specific case-insensitive path with a file. + */ +- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; + +/** + * Adds a handler to the server to respond to incoming "GET" HTTP requests + * with a case-insensitive path inside a base path with the corresponding file + * inside a local directory. If no local file matches the request path, a 401 + * HTTP status code is returned to the client. + * + * The "indexFilename" argument allows to specify an "index" file name to use + * when the request path corresponds to a directory. + */ +- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(nullable NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests; + +@end + +/** + * GCDWebServer provides its own built-in logging facility which is used by + * default. It simply sends log messages to stderr assuming it is connected + * to a terminal type device. + * + * GCDWebServer is also compatible with a limited set of third-party logging + * facilities. If one of them is available at compile time, GCDWebServer will + * automatically use it in place of the built-in one. + * + * Currently supported third-party logging facilities are: + * - XLFacility (by the same author as GCDWebServer): https://github.com/swisspol/XLFacility + * + * For the built-in logging facility, the default logging level is INFO + * (or DEBUG if the preprocessor constant "DEBUG" evaluates to non-zero at + * compile time). + * + * It's possible to have GCDWebServer use a custom logging facility by defining + * the "__GCDWEBSERVER_LOGGING_HEADER__" preprocessor constant in Xcode build + * settings to the name of a custom header file (escaped like \"MyLogging.h\"). + * This header file must define the following set of macros: + * + * GWS_LOG_DEBUG(...) + * GWS_LOG_VERBOSE(...) + * GWS_LOG_INFO(...) + * GWS_LOG_WARNING(...) + * GWS_LOG_ERROR(...) + * + * IMPORTANT: These macros must behave like NSLog(). Furthermore the GWS_LOG_DEBUG() + * macro should not do anything unless the preprocessor constant "DEBUG" evaluates + * to non-zero. + * + * The logging methods below send log messages to the same logging facility + * used by GCDWebServer. They can be used for consistency wherever you interact + * with GCDWebServer in your code (e.g. in the implementation of handlers). + */ +@interface GCDWebServer (Logging) + +/** + * Sets the log level of the logging facility below which log messages are discarded. + * + * @warning The interpretation of the "level" argument depends on the logging + * facility used at compile time. + * + * If using the built-in logging facility, the log levels are as follow: + * DEBUG = 0 + * VERBOSE = 1 + * INFO = 2 + * WARNING = 3 + * ERROR = 4 + */ ++ (void)setLogLevel:(int)level; + +/** + * Logs a message to the logging facility at the VERBOSE level. + */ +- (void)logVerbose:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); + +/** + * Logs a message to the logging facility at the INFO level. + */ +- (void)logInfo:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); + +/** + * Logs a message to the logging facility at the WARNING level. + */ +- (void)logWarning:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); + +/** + * Logs a message to the logging facility at the ERROR level. + */ +- (void)logError:(NSString*)format, ... NS_FORMAT_FUNCTION(1, 2); + +@end + +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + +@interface GCDWebServer (Testing) + +/** + * Activates recording of HTTP requests and responses which create files in the + * current directory containing the raw data for all requests and responses. + * + * @warning The current directory must not contain any prior recording files. + */ +@property(nonatomic, getter=isRecordingEnabled) BOOL recordingEnabled; + +/** + * Runs tests by playing back pre-recorded HTTP requests in the given directory + * and comparing the generated responses with the pre-recorded ones. + * + * Returns the number of failed tests or -1 if server failed to start. + */ +- (NSInteger)runTestsWithOptions:(nullable NSDictionary*)options inDirectory:(NSString*)path; + +@end + +#endif + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServer.m b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServer.m new file mode 100644 index 0000000000..0b755577bd --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServer.m @@ -0,0 +1,1292 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import +#if TARGET_OS_IPHONE +#import +#else +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ +#import +#endif +#endif +#import +#import + +#import "GCDWebServerPrivate.h" + +#if TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR +#define kDefaultPort 80 +#else +#define kDefaultPort 8080 +#endif + +#define kBonjourResolutionTimeout 5.0 + +NSString* const GCDWebServerOption_Port = @"Port"; +NSString* const GCDWebServerOption_BonjourName = @"BonjourName"; +NSString* const GCDWebServerOption_BonjourType = @"BonjourType"; +NSString* const GCDWebServerOption_RequestNATPortMapping = @"RequestNATPortMapping"; +NSString* const GCDWebServerOption_BindToLocalhost = @"BindToLocalhost"; +NSString* const GCDWebServerOption_MaxPendingConnections = @"MaxPendingConnections"; +NSString* const GCDWebServerOption_ServerName = @"ServerName"; +NSString* const GCDWebServerOption_AuthenticationMethod = @"AuthenticationMethod"; +NSString* const GCDWebServerOption_AuthenticationRealm = @"AuthenticationRealm"; +NSString* const GCDWebServerOption_AuthenticationAccounts = @"AuthenticationAccounts"; +NSString* const GCDWebServerOption_ConnectionClass = @"ConnectionClass"; +NSString* const GCDWebServerOption_AutomaticallyMapHEADToGET = @"AutomaticallyMapHEADToGET"; +NSString* const GCDWebServerOption_ConnectedStateCoalescingInterval = @"ConnectedStateCoalescingInterval"; +NSString* const GCDWebServerOption_DispatchQueuePriority = @"DispatchQueuePriority"; +#if TARGET_OS_IPHONE +NSString* const GCDWebServerOption_AutomaticallySuspendInBackground = @"AutomaticallySuspendInBackground"; +#endif + +NSString* const GCDWebServerAuthenticationMethod_Basic = @"Basic"; +NSString* const GCDWebServerAuthenticationMethod_DigestAccess = @"DigestAccess"; + +#if defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__) +#if DEBUG +GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Debug; +#else +GCDWebServerLoggingLevel GCDWebServerLogLevel = kGCDWebServerLoggingLevel_Info; +#endif +#endif + +#if !TARGET_OS_IPHONE +static BOOL _run; +#endif + +#ifdef __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__ + +void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* format, ...) { + static const char* levelNames[] = {"DEBUG", "VERBOSE", "INFO", "WARNING", "ERROR"}; + static int enableLogging = -1; + if (enableLogging < 0) { + enableLogging = (isatty(STDERR_FILENO) ? 1 : 0); + } + if (enableLogging) { + va_list arguments; + va_start(arguments, format); + NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; + va_end(arguments); + fprintf(stderr, "[%s] %s\n", levelNames[level], [message UTF8String]); + } +} + +#endif + +#if !TARGET_OS_IPHONE + +static void _SignalHandler(int signal) { + _run = NO; + printf("\n"); +} + +#endif + +#if !TARGET_OS_IPHONE || defined(__GCDWEBSERVER_ENABLE_TESTING__) + +// This utility function is used to ensure scheduled callbacks on the main thread are called when running the server synchronously +// https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html +// The main queue works with the application’s run loop to interleave the execution of queued tasks with the execution of other event sources attached to the run loop +// TODO: Ensure all scheduled blocks on the main queue are also executed +static void _ExecuteMainThreadRunLoopSources() { + SInt32 result; + do { + result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, true); + } while (result == kCFRunLoopRunHandledSource); +} + +#endif + +@implementation GCDWebServerHandler + +- (instancetype)initWithMatchBlock:(GCDWebServerMatchBlock _Nonnull)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock _Nonnull)processBlock { + if ((self = [super init])) { + _matchBlock = [matchBlock copy]; + _asyncProcessBlock = [processBlock copy]; + } + return self; +} + +@end + +@implementation GCDWebServer { + dispatch_queue_t _syncQueue; + dispatch_group_t _sourceGroup; + NSMutableArray* _handlers; + NSInteger _activeConnections; // Accessed through _syncQueue only + BOOL _connected; // Accessed on main thread only + CFRunLoopTimerRef _disconnectTimer; // Accessed on main thread only + + NSDictionary* _options; + NSMutableDictionary* _authenticationBasicAccounts; + NSMutableDictionary* _authenticationDigestAccounts; + Class _connectionClass; + CFTimeInterval _disconnectDelay; + dispatch_source_t _source4; + dispatch_source_t _source6; + CFNetServiceRef _registrationService; + CFNetServiceRef _resolutionService; + DNSServiceRef _dnsService; + CFSocketRef _dnsSocket; + CFRunLoopSourceRef _dnsSource; + NSString* _dnsAddress; + NSUInteger _dnsPort; + BOOL _bindToLocalhost; +#if TARGET_OS_IPHONE + BOOL _suspendInBackground; + UIBackgroundTaskIdentifier _backgroundTask; +#endif +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + BOOL _recording; +#endif +} + ++ (void)initialize { + GCDWebServerInitializeFunctions(); +} + +- (instancetype)init { + if ((self = [super init])) { + _syncQueue = dispatch_queue_create([NSStringFromClass([self class]) UTF8String], DISPATCH_QUEUE_SERIAL); + _sourceGroup = dispatch_group_create(); + _handlers = [[NSMutableArray alloc] init]; +#if TARGET_OS_IPHONE + _backgroundTask = UIBackgroundTaskInvalid; +#endif + } + return self; +} + +- (void)dealloc { + GWS_DCHECK(_connected == NO); + GWS_DCHECK(_activeConnections == 0); + GWS_DCHECK(_options == nil); // The server can never be dealloc'ed while running because of the retain-cycle with the dispatch source + GWS_DCHECK(_disconnectTimer == NULL); // The server can never be dealloc'ed while the disconnect timer is pending because of the retain-cycle + +#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE + dispatch_release(_sourceGroup); + dispatch_release(_syncQueue); +#endif +} + +#if TARGET_OS_IPHONE + +// Always called on main thread +- (void)_startBackgroundTask { + GWS_DCHECK([NSThread isMainThread]); + if (_backgroundTask == UIBackgroundTaskInvalid) { + GWS_LOG_DEBUG(@"Did start background task"); + _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ + GWS_LOG_WARNING(@"Application is being suspended while %@ is still connected", [self class]); + [self _endBackgroundTask]; + }]; + } else { + GWS_DNOT_REACHED(); + } +} + +#endif + +// Always called on main thread +- (void)_didConnect { + GWS_DCHECK([NSThread isMainThread]); + GWS_DCHECK(_connected == NO); + _connected = YES; + GWS_LOG_DEBUG(@"Did connect"); + +#if TARGET_OS_IPHONE + if ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) { + [self _startBackgroundTask]; + } +#endif + + if ([_delegate respondsToSelector:@selector(webServerDidConnect:)]) { + [_delegate webServerDidConnect:self]; + } +} + +- (void)willStartConnection:(GCDWebServerConnection*)connection { + dispatch_sync(_syncQueue, ^{ + GWS_DCHECK(self->_activeConnections >= 0); + if (self->_activeConnections == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_disconnectTimer) { + CFRunLoopTimerInvalidate(self->_disconnectTimer); + CFRelease(self->_disconnectTimer); + self->_disconnectTimer = NULL; + } + if (self->_connected == NO) { + [self _didConnect]; + } + }); + } + self->_activeConnections += 1; + }); +} + +#if TARGET_OS_IPHONE + +// Always called on main thread +- (void)_endBackgroundTask { + GWS_DCHECK([NSThread isMainThread]); + if (_backgroundTask != UIBackgroundTaskInvalid) { + if (_suspendInBackground && ([[UIApplication sharedApplication] applicationState] == UIApplicationStateBackground) && _source4) { + [self _stop]; + } + [[UIApplication sharedApplication] endBackgroundTask:_backgroundTask]; + _backgroundTask = UIBackgroundTaskInvalid; + GWS_LOG_DEBUG(@"Did end background task"); + } +} + +#endif + +// Always called on main thread +- (void)_didDisconnect { + GWS_DCHECK([NSThread isMainThread]); + GWS_DCHECK(_connected == YES); + _connected = NO; + GWS_LOG_DEBUG(@"Did disconnect"); + +#if TARGET_OS_IPHONE + [self _endBackgroundTask]; +#endif + + if ([_delegate respondsToSelector:@selector(webServerDidDisconnect:)]) { + [_delegate webServerDidDisconnect:self]; + } +} + +- (void)didEndConnection:(GCDWebServerConnection*)connection { + dispatch_sync(_syncQueue, ^{ + GWS_DCHECK(self->_activeConnections > 0); + self->_activeConnections -= 1; + if (self->_activeConnections == 0) { + dispatch_async(dispatch_get_main_queue(), ^{ + if ((self->_disconnectDelay > 0.0) && (self->_source4 != NULL)) { + if (self->_disconnectTimer) { + CFRunLoopTimerInvalidate(self->_disconnectTimer); + CFRelease(self->_disconnectTimer); + } + self->_disconnectTimer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + self->_disconnectDelay, 0.0, 0, 0, ^(CFRunLoopTimerRef timer) { + GWS_DCHECK([NSThread isMainThread]); + [self _didDisconnect]; + CFRelease(self->_disconnectTimer); + self->_disconnectTimer = NULL; + }); + CFRunLoopAddTimer(CFRunLoopGetMain(), self->_disconnectTimer, kCFRunLoopCommonModes); + } else { + [self _didDisconnect]; + } + }); + } + }); +} + +- (NSString*)bonjourName { + CFStringRef name = _resolutionService ? CFNetServiceGetName(_resolutionService) : NULL; + return name && CFStringGetLength(name) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, name)) : nil; +} + +- (NSString*)bonjourType { + CFStringRef type = _resolutionService ? CFNetServiceGetType(_resolutionService) : NULL; + return type && CFStringGetLength(type) ? CFBridgingRelease(CFStringCreateCopy(kCFAllocatorDefault, type)) : nil; +} + +- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock processBlock:(GCDWebServerProcessBlock)processBlock { + [self addHandlerWithMatchBlock:matchBlock + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + completionBlock(processBlock(request)); + }]; +} + +- (void)addHandlerWithMatchBlock:(GCDWebServerMatchBlock)matchBlock asyncProcessBlock:(GCDWebServerAsyncProcessBlock)processBlock { + GWS_DCHECK(_options == nil); + GCDWebServerHandler* handler = [[GCDWebServerHandler alloc] initWithMatchBlock:matchBlock asyncProcessBlock:processBlock]; + [_handlers insertObject:handler atIndex:0]; +} + +- (void)removeAllHandlers { + GWS_DCHECK(_options == nil); + [_handlers removeAllObjects]; +} + +static void _NetServiceRegisterCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { + GWS_DCHECK([NSThread isMainThread]); + @autoreleasepool { + if (error->error) { + GWS_LOG_ERROR(@"Bonjour registration error %i (domain %i)", (int)error->error, (int)error->domain); + } else { + GCDWebServer* server = (__bridge GCDWebServer*)info; + GWS_LOG_VERBOSE(@"Bonjour registration complete for %@", [server class]); + if (!CFNetServiceResolveWithTimeout(server->_resolutionService, kBonjourResolutionTimeout, NULL)) { + GWS_LOG_ERROR(@"Failed starting Bonjour resolution"); + GWS_DNOT_REACHED(); + } + } + } +} + +static void _NetServiceResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info) { + GWS_DCHECK([NSThread isMainThread]); + @autoreleasepool { + if (error->error) { + if ((error->domain != kCFStreamErrorDomainNetServices) && (error->error != kCFNetServicesErrorTimeout)) { + GWS_LOG_ERROR(@"Bonjour resolution error %i (domain %i)", (int)error->error, (int)error->domain); + } + } else { + GCDWebServer* server = (__bridge GCDWebServer*)info; + GWS_LOG_INFO(@"%@ now locally reachable at %@", [server class], server.bonjourServerURL); + if ([server.delegate respondsToSelector:@selector(webServerDidCompleteBonjourRegistration:)]) { + [server.delegate webServerDidCompleteBonjourRegistration:server]; + } + } + } +} + +static void _DNSServiceCallBack(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, uint32_t externalAddress, DNSServiceProtocol protocol, uint16_t internalPort, uint16_t externalPort, uint32_t ttl, void* context) { + GWS_DCHECK([NSThread isMainThread]); + @autoreleasepool { + GCDWebServer* server = (__bridge GCDWebServer*)context; + if ((errorCode == kDNSServiceErr_NoError) || (errorCode == kDNSServiceErr_DoubleNAT)) { + struct sockaddr_in addr4; + bzero(&addr4, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_addr.s_addr = externalAddress; // Already in network byte order + server->_dnsAddress = GCDWebServerStringFromSockAddr((const struct sockaddr*)&addr4, NO); + server->_dnsPort = ntohs(externalPort); + GWS_LOG_INFO(@"%@ now publicly reachable at %@", [server class], server.publicServerURL); + } else { + GWS_LOG_ERROR(@"DNS service error %i", errorCode); + server->_dnsAddress = nil; + server->_dnsPort = 0; + } + if ([server.delegate respondsToSelector:@selector(webServerDidUpdateNATPortMapping:)]) { + [server.delegate webServerDidUpdateNATPortMapping:server]; + } + } +} + +static void _SocketCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void* data, void* info) { + GWS_DCHECK([NSThread isMainThread]); + @autoreleasepool { + GCDWebServer* server = (__bridge GCDWebServer*)info; + DNSServiceErrorType status = DNSServiceProcessResult(server->_dnsService); + if (status != kDNSServiceErr_NoError) { + GWS_LOG_ERROR(@"DNS service error %i", status); + } + } +} + +static inline id _GetOption(NSDictionary* options, NSString* key, id defaultValue) { + id value = [options objectForKey:key]; + return value ? value : defaultValue; +} + +static inline NSString* _EncodeBase64(NSString* string) { + NSData* data = [string dataUsingEncoding:NSUTF8StringEncoding]; +#if TARGET_OS_IPHONE || (__MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9) + return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]; +#else + if (@available(macOS 10.9, *)) { + return [[NSString alloc] initWithData:[data base64EncodedDataWithOptions:0] encoding:NSASCIIStringEncoding]; + } + return [data base64Encoding]; +#endif +} + +- (int)_createListeningSocket:(BOOL)useIPv6 + localAddress:(const void*)address + length:(socklen_t)length + maxPendingConnections:(NSUInteger)maxPendingConnections + error:(NSError**)error { + int listeningSocket = socket(useIPv6 ? PF_INET6 : PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listeningSocket > 0) { + int yes = 1; + setsockopt(listeningSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + if (bind(listeningSocket, address, length) == 0) { + if (listen(listeningSocket, (int)maxPendingConnections) == 0) { + GWS_LOG_DEBUG(@"Did open %s listening socket %i", useIPv6 ? "IPv6" : "IPv4", listeningSocket); + return listeningSocket; + } else { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + GWS_LOG_ERROR(@"Failed starting %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + close(listeningSocket); + } + } else { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + GWS_LOG_ERROR(@"Failed binding %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + close(listeningSocket); + } + + } else { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + GWS_LOG_ERROR(@"Failed creating %s listening socket: %s (%i)", useIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + } + return -1; +} + +- (dispatch_source_t)_createDispatchSourceWithListeningSocket:(int)listeningSocket isIPv6:(BOOL)isIPv6 { + dispatch_group_enter(_sourceGroup); + dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, listeningSocket, 0, dispatch_get_global_queue(_dispatchQueuePriority, 0)); + dispatch_source_set_cancel_handler(source, ^{ + @autoreleasepool { + int result = close(listeningSocket); + if (result != 0) { + GWS_LOG_ERROR(@"Failed closing %s listening socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + } else { + GWS_LOG_DEBUG(@"Did close %s listening socket %i", isIPv6 ? "IPv6" : "IPv4", listeningSocket); + } + } + dispatch_group_leave(self->_sourceGroup); + }); + dispatch_source_set_event_handler(source, ^{ + @autoreleasepool { + struct sockaddr_storage remoteSockAddr; + socklen_t remoteAddrLen = sizeof(remoteSockAddr); + int socket = accept(listeningSocket, (struct sockaddr*)&remoteSockAddr, &remoteAddrLen); + if (socket > 0) { + NSData* remoteAddress = [NSData dataWithBytes:&remoteSockAddr length:remoteAddrLen]; + + struct sockaddr_storage localSockAddr; + socklen_t localAddrLen = sizeof(localSockAddr); + NSData* localAddress = nil; + if (getsockname(socket, (struct sockaddr*)&localSockAddr, &localAddrLen) == 0) { + localAddress = [NSData dataWithBytes:&localSockAddr length:localAddrLen]; + GWS_DCHECK((!isIPv6 && localSockAddr.ss_family == AF_INET) || (isIPv6 && localSockAddr.ss_family == AF_INET6)); + } else { + GWS_DNOT_REACHED(); + } + + int noSigPipe = 1; + setsockopt(socket, SOL_SOCKET, SO_NOSIGPIPE, &noSigPipe, sizeof(noSigPipe)); // Make sure this socket cannot generate SIG_PIPE + + GCDWebServerConnection* connection = [(GCDWebServerConnection*)[self->_connectionClass alloc] initWithServer:self localAddress:localAddress remoteAddress:remoteAddress socket:socket]; // Connection will automatically retain itself while opened + [connection self]; // Prevent compiler from complaining about unused variable / useless statement + } else { + GWS_LOG_ERROR(@"Failed accepting %s socket: %s (%i)", isIPv6 ? "IPv6" : "IPv4", strerror(errno), errno); + } + } + }); + return source; +} + +- (BOOL)_start:(NSError**)error { + GWS_DCHECK(_source4 == NULL); + + NSUInteger port = [(NSNumber*)_GetOption(_options, GCDWebServerOption_Port, @0) unsignedIntegerValue]; + BOOL bindToLocalhost = [(NSNumber*)_GetOption(_options, GCDWebServerOption_BindToLocalhost, @NO) boolValue]; + NSUInteger maxPendingConnections = [(NSNumber*)_GetOption(_options, GCDWebServerOption_MaxPendingConnections, @16) unsignedIntegerValue]; + + struct sockaddr_in addr4; + bzero(&addr4, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port); + addr4.sin_addr.s_addr = bindToLocalhost ? htonl(INADDR_LOOPBACK) : htonl(INADDR_ANY); + int listeningSocket4 = [self _createListeningSocket:NO localAddress:&addr4 length:sizeof(addr4) maxPendingConnections:maxPendingConnections error:error]; + if (listeningSocket4 <= 0) { + return NO; + } + if (port == 0) { + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + if (getsockname(listeningSocket4, (struct sockaddr*)&addr, &addrlen) == 0) { + port = ntohs(addr.sin_port); + } else { + GWS_LOG_ERROR(@"Failed retrieving socket address: %s (%i)", strerror(errno), errno); + } + } + + struct sockaddr_in6 addr6; + bzero(&addr6, sizeof(addr6)); + addr6.sin6_len = sizeof(addr6); + addr6.sin6_family = AF_INET6; + addr6.sin6_port = htons(port); + addr6.sin6_addr = bindToLocalhost ? in6addr_loopback : in6addr_any; + int listeningSocket6 = [self _createListeningSocket:YES localAddress:&addr6 length:sizeof(addr6) maxPendingConnections:maxPendingConnections error:error]; + if (listeningSocket6 <= 0) { + close(listeningSocket4); + return NO; + } + + _serverName = [(NSString*)_GetOption(_options, GCDWebServerOption_ServerName, NSStringFromClass([self class])) copy]; + NSString* authenticationMethod = _GetOption(_options, GCDWebServerOption_AuthenticationMethod, nil); + if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_Basic]) { + _authenticationRealm = [(NSString*)_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; + _authenticationBasicAccounts = [[NSMutableDictionary alloc] init]; + NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); + [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { + [self->_authenticationBasicAccounts setObject:_EncodeBase64([NSString stringWithFormat:@"%@:%@", username, password]) forKey:username]; + }]; + } else if ([authenticationMethod isEqualToString:GCDWebServerAuthenticationMethod_DigestAccess]) { + _authenticationRealm = [(NSString*)_GetOption(_options, GCDWebServerOption_AuthenticationRealm, _serverName) copy]; + _authenticationDigestAccounts = [[NSMutableDictionary alloc] init]; + NSDictionary* accounts = _GetOption(_options, GCDWebServerOption_AuthenticationAccounts, @{}); + [accounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* password, BOOL* stop) { + [self->_authenticationDigestAccounts setObject:GCDWebServerComputeMD5Digest(@"%@:%@:%@", username, self->_authenticationRealm, password) forKey:username]; + }]; + } + _connectionClass = _GetOption(_options, GCDWebServerOption_ConnectionClass, [GCDWebServerConnection class]); + _shouldAutomaticallyMapHEADToGET = [(NSNumber*)_GetOption(_options, GCDWebServerOption_AutomaticallyMapHEADToGET, @YES) boolValue]; + _disconnectDelay = [(NSNumber*)_GetOption(_options, GCDWebServerOption_ConnectedStateCoalescingInterval, @1.0) doubleValue]; + _dispatchQueuePriority = [(NSNumber*)_GetOption(_options, GCDWebServerOption_DispatchQueuePriority, @(DISPATCH_QUEUE_PRIORITY_DEFAULT)) longValue]; + + _source4 = [self _createDispatchSourceWithListeningSocket:listeningSocket4 isIPv6:NO]; + _source6 = [self _createDispatchSourceWithListeningSocket:listeningSocket6 isIPv6:YES]; + _port = port; + _bindToLocalhost = bindToLocalhost; + + NSString* bonjourName = _GetOption(_options, GCDWebServerOption_BonjourName, nil); + NSString* bonjourType = _GetOption(_options, GCDWebServerOption_BonjourType, @"_http._tcp"); + if (bonjourName) { + _registrationService = CFNetServiceCreate(kCFAllocatorDefault, CFSTR("local."), (__bridge CFStringRef)bonjourType, (__bridge CFStringRef)(bonjourName.length ? bonjourName : _serverName), (SInt32)_port); + if (_registrationService) { + CFNetServiceClientContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; + + CFNetServiceSetClient(_registrationService, _NetServiceRegisterCallBack, &context); + CFNetServiceScheduleWithRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFStreamError streamError = {0}; + CFNetServiceRegisterWithOptions(_registrationService, 0, &streamError); + + _resolutionService = CFNetServiceCreateCopy(kCFAllocatorDefault, _registrationService); + if (_resolutionService) { + CFNetServiceSetClient(_resolutionService, _NetServiceResolveCallBack, &context); + CFNetServiceScheduleWithRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + } else { + GWS_LOG_ERROR(@"Failed creating CFNetService for resolution"); + } + } else { + GWS_LOG_ERROR(@"Failed creating CFNetService for registration"); + } + } + + if ([(NSNumber*)_GetOption(_options, GCDWebServerOption_RequestNATPortMapping, @NO) boolValue]) { + DNSServiceErrorType status = DNSServiceNATPortMappingCreate(&_dnsService, 0, 0, kDNSServiceProtocol_TCP, htons(port), htons(port), 0, _DNSServiceCallBack, (__bridge void*)self); + if (status == kDNSServiceErr_NoError) { + CFSocketContext context = {0, (__bridge void*)self, NULL, NULL, NULL}; + _dnsSocket = CFSocketCreateWithNative(kCFAllocatorDefault, DNSServiceRefSockFD(_dnsService), kCFSocketReadCallBack, _SocketCallBack, &context); + if (_dnsSocket) { + CFSocketSetSocketFlags(_dnsSocket, CFSocketGetSocketFlags(_dnsSocket) & ~kCFSocketCloseOnInvalidate); + _dnsSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _dnsSocket, 0); + if (_dnsSource) { + CFRunLoopAddSource(CFRunLoopGetMain(), _dnsSource, kCFRunLoopCommonModes); + } else { + GWS_LOG_ERROR(@"Failed creating CFRunLoopSource"); + GWS_DNOT_REACHED(); + } + } else { + GWS_LOG_ERROR(@"Failed creating CFSocket"); + GWS_DNOT_REACHED(); + } + } else { + GWS_LOG_ERROR(@"Failed creating NAT port mapping (%i)", status); + } + } + + dispatch_resume(_source4); + dispatch_resume(_source6); + GWS_LOG_INFO(@"%@ started on port %i and reachable at %@", [self class], (int)_port, self.serverURL); + if ([_delegate respondsToSelector:@selector(webServerDidStart:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_delegate webServerDidStart:self]; + }); + } + + return YES; +} + +- (void)_stop { + GWS_DCHECK(_source4 != NULL); + + if (_dnsService) { + _dnsAddress = nil; + _dnsPort = 0; + if (_dnsSource) { + CFRunLoopSourceInvalidate(_dnsSource); + CFRelease(_dnsSource); + _dnsSource = NULL; + } + if (_dnsSocket) { + CFRelease(_dnsSocket); + _dnsSocket = NULL; + } + DNSServiceRefDeallocate(_dnsService); + _dnsService = NULL; + } + + if (_registrationService) { + if (_resolutionService) { + CFNetServiceUnscheduleFromRunLoop(_resolutionService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFNetServiceSetClient(_resolutionService, NULL, NULL); + CFNetServiceCancel(_resolutionService); + CFRelease(_resolutionService); + _resolutionService = NULL; + } + CFNetServiceUnscheduleFromRunLoop(_registrationService, CFRunLoopGetMain(), kCFRunLoopCommonModes); + CFNetServiceSetClient(_registrationService, NULL, NULL); + CFNetServiceCancel(_registrationService); + CFRelease(_registrationService); + _registrationService = NULL; + } + + dispatch_source_cancel(_source6); + dispatch_source_cancel(_source4); + dispatch_group_wait(_sourceGroup, DISPATCH_TIME_FOREVER); // Wait until the cancellation handlers have been called which guarantees the listening sockets are closed +#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE + dispatch_release(_source6); +#endif + _source6 = NULL; +#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE + dispatch_release(_source4); +#endif + _source4 = NULL; + _port = 0; + _bindToLocalhost = NO; + + _serverName = nil; + _authenticationRealm = nil; + _authenticationBasicAccounts = nil; + _authenticationDigestAccounts = nil; + + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_disconnectTimer) { + CFRunLoopTimerInvalidate(self->_disconnectTimer); + CFRelease(self->_disconnectTimer); + self->_disconnectTimer = NULL; + [self _didDisconnect]; + } + }); + + GWS_LOG_INFO(@"%@ stopped", [self class]); + if ([_delegate respondsToSelector:@selector(webServerDidStop:)]) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_delegate webServerDidStop:self]; + }); + } +} + +#if TARGET_OS_IPHONE + +- (void)_didEnterBackground:(NSNotification*)notification { + GWS_DCHECK([NSThread isMainThread]); + GWS_LOG_DEBUG(@"Did enter background"); + if ((_backgroundTask == UIBackgroundTaskInvalid) && _source4) { + [self _stop]; + } +} + +- (void)_willEnterForeground:(NSNotification*)notification { + GWS_DCHECK([NSThread isMainThread]); + GWS_LOG_DEBUG(@"Will enter foreground"); + if (!_source4) { + [self _start:NULL]; // TODO: There's probably nothing we can do on failure + } +} + +#endif + +- (BOOL)startWithOptions:(NSDictionary*)options error:(NSError**)error { + if (_options == nil) { + _options = options ? [options copy] : @{}; +#if TARGET_OS_IPHONE + _suspendInBackground = [(NSNumber*)_GetOption(_options, GCDWebServerOption_AutomaticallySuspendInBackground, @YES) boolValue]; + if (((_suspendInBackground == NO) || ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground)) && ![self _start:error]) +#else + if (![self _start:error]) +#endif + { + _options = nil; + return NO; + } +#if TARGET_OS_IPHONE + if (_suspendInBackground) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_willEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; + } +#endif + return YES; + } else { + GWS_DNOT_REACHED(); + } + return NO; +} + +- (BOOL)isRunning { + return (_options ? YES : NO); +} + +- (void)stop { + if (_options) { +#if TARGET_OS_IPHONE + if (_suspendInBackground) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + } +#endif + if (_source4) { + [self _stop]; + } + _options = nil; + } else { + GWS_DNOT_REACHED(); + } +} + +@end + +@implementation GCDWebServer (Extensions) + +- (NSURL*)serverURL { + if (_source4) { + NSString* ipAddress = _bindToLocalhost ? @"localhost" : GCDWebServerGetPrimaryIPAddress(NO); // We can't really use IPv6 anyway as it doesn't work great with HTTP URLs in practice + if (ipAddress) { + if (_port != 80) { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", ipAddress, (int)_port]]; + } else { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", ipAddress]]; + } + } + } + return nil; +} + +- (NSURL*)bonjourServerURL { + if (_source4 && _resolutionService) { + NSString* name = (__bridge NSString*)CFNetServiceGetTargetHost(_resolutionService); + if (name.length) { + name = [name substringToIndex:(name.length - 1)]; // Strip trailing period at end of domain + if (_port != 80) { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", name, (int)_port]]; + } else { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", name]]; + } + } + } + return nil; +} + +- (NSURL*)publicServerURL { + if (_source4 && _dnsService && _dnsAddress && _dnsPort) { + if (_dnsPort != 80) { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@:%i/", _dnsAddress, (int)_dnsPort]]; + } else { + return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@/", _dnsAddress]]; + } + } + return nil; +} + +- (BOOL)start { + return [self startWithPort:kDefaultPort bonjourName:@""]; +} + +- (BOOL)startWithPort:(NSUInteger)port bonjourName:(NSString*)name { + NSMutableDictionary* options = [NSMutableDictionary dictionary]; + [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port]; + [options setValue:name forKey:GCDWebServerOption_BonjourName]; + return [self startWithOptions:options error:NULL]; +} + +#if !TARGET_OS_IPHONE + +- (BOOL)runWithPort:(NSUInteger)port bonjourName:(NSString*)name { + NSMutableDictionary* options = [NSMutableDictionary dictionary]; + [options setObject:[NSNumber numberWithInteger:port] forKey:GCDWebServerOption_Port]; + [options setValue:name forKey:GCDWebServerOption_BonjourName]; + return [self runWithOptions:options error:NULL]; +} + +- (BOOL)runWithOptions:(NSDictionary*)options error:(NSError**)error { + GWS_DCHECK([NSThread isMainThread]); + BOOL success = NO; + _run = YES; + void (*termHandler)(int) = signal(SIGTERM, _SignalHandler); + void (*intHandler)(int) = signal(SIGINT, _SignalHandler); + if ((termHandler != SIG_ERR) && (intHandler != SIG_ERR)) { + if ([self startWithOptions:options error:error]) { + while (_run) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true); + } + [self stop]; + success = YES; + } + _ExecuteMainThreadRunLoopSources(); + signal(SIGINT, intHandler); + signal(SIGTERM, termHandler); + } + return success; +} + +#endif + +@end + +@implementation GCDWebServer (Handlers) + +- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { + [self addDefaultHandlerForMethod:method + requestClass:aClass + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + completionBlock(block(request)); + }]; +} + +- (void)addDefaultHandlerForMethod:(NSString*)method requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { + [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:method]) { + return nil; + } + return [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + } + asyncProcessBlock:block]; +} + +- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { + [self addHandlerForMethod:method + path:path + requestClass:aClass + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + completionBlock(block(request)); + }]; +} + +- (void)addHandlerForMethod:(NSString*)method path:(NSString*)path requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { + if ([path hasPrefix:@"/"] && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) { + [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:method]) { + return nil; + } + if ([urlPath caseInsensitiveCompare:path] != NSOrderedSame) { + return nil; + } + return [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + } + asyncProcessBlock:block]; + } else { + GWS_DNOT_REACHED(); + } +} + +- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass processBlock:(GCDWebServerProcessBlock)block { + [self addHandlerForMethod:method + pathRegex:regex + requestClass:aClass + asyncProcessBlock:^(GCDWebServerRequest* request, GCDWebServerCompletionBlock completionBlock) { + completionBlock(block(request)); + }]; +} + +- (void)addHandlerForMethod:(NSString*)method pathRegex:(NSString*)regex requestClass:(Class)aClass asyncProcessBlock:(GCDWebServerAsyncProcessBlock)block { + NSRegularExpression* expression = [NSRegularExpression regularExpressionWithPattern:regex options:NSRegularExpressionCaseInsensitive error:NULL]; + if (expression && [aClass isSubclassOfClass:[GCDWebServerRequest class]]) { + [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:method]) { + return nil; + } + + NSArray* matches = [expression matchesInString:urlPath options:0 range:NSMakeRange(0, urlPath.length)]; + if (matches.count == 0) { + return nil; + } + + NSMutableArray* captures = [NSMutableArray array]; + for (NSTextCheckingResult* result in matches) { + // Start at 1; index 0 is the whole string + for (NSUInteger i = 1; i < result.numberOfRanges; i++) { + NSRange range = [result rangeAtIndex:i]; + // range is {NSNotFound, 0} "if one of the capture groups did not participate in this particular match" + // see discussion in -[NSRegularExpression firstMatchInString:options:range:] + if (range.location != NSNotFound) { + [captures addObject:[urlPath substringWithRange:range]]; + } + } + } + + GCDWebServerRequest* request = [(GCDWebServerRequest*)[aClass alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + [request setAttribute:captures forKey:GCDWebServerRequestAttribute_RegexCaptures]; + return request; + } + asyncProcessBlock:block]; + } else { + GWS_DNOT_REACHED(); + } +} + +@end + +@implementation GCDWebServer (GETHandlers) + +- (void)addGETHandlerForPath:(NSString*)path staticData:(NSData*)staticData contentType:(NSString*)contentType cacheAge:(NSUInteger)cacheAge { + [self addHandlerForMethod:@"GET" + path:path + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) { + GCDWebServerResponse* response = [GCDWebServerDataResponse responseWithData:staticData contentType:contentType]; + response.cacheControlMaxAge = cacheAge; + return response; + }]; +} + +- (void)addGETHandlerForPath:(NSString*)path filePath:(NSString*)filePath isAttachment:(BOOL)isAttachment cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests { + [self addHandlerForMethod:@"GET" + path:path + requestClass:[GCDWebServerRequest class] + processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) { + GCDWebServerResponse* response = nil; + if (allowRangeRequests) { + response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange isAttachment:isAttachment]; + [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; + } else { + response = [GCDWebServerFileResponse responseWithFile:filePath isAttachment:isAttachment]; + } + response.cacheControlMaxAge = cacheAge; + return response; + }]; +} + +- (GCDWebServerResponse*)_responseWithContentsOfDirectory:(NSString*)path { + NSArray* contents = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL] sortedArrayUsingSelector:@selector(localizedStandardCompare:)]; + if (contents == nil) { + return nil; + } + NSMutableString* html = [NSMutableString string]; + [html appendString:@"\n"]; + [html appendString:@"\n"]; + [html appendString:@"
    \n"]; + for (NSString* entry in contents) { + if (![entry hasPrefix:@"."]) { + NSString* type = [[[NSFileManager defaultManager] attributesOfItemAtPath:[path stringByAppendingPathComponent:entry] error:NULL] objectForKey:NSFileType]; + GWS_DCHECK(type); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + NSString* escapedFile = [entry stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; +#pragma clang diagnostic pop + GWS_DCHECK(escapedFile); + if ([type isEqualToString:NSFileTypeRegular]) { + [html appendFormat:@"
  • %@
  • \n", escapedFile, entry]; + } else if ([type isEqualToString:NSFileTypeDirectory]) { + [html appendFormat:@"
  • %@/
  • \n", escapedFile, entry]; + } + } + } + [html appendString:@"
\n"]; + [html appendString:@"\n"]; + return [GCDWebServerDataResponse responseWithHTML:html]; +} + +- (void)addGETHandlerForBasePath:(NSString*)basePath directoryPath:(NSString*)directoryPath indexFilename:(NSString*)indexFilename cacheAge:(NSUInteger)cacheAge allowRangeRequests:(BOOL)allowRangeRequests { + if ([basePath hasPrefix:@"/"] && [basePath hasSuffix:@"/"]) { + GCDWebServer* __unsafe_unretained server = self; + [self addHandlerWithMatchBlock:^GCDWebServerRequest*(NSString* requestMethod, NSURL* requestURL, NSDictionary* requestHeaders, NSString* urlPath, NSDictionary* urlQuery) { + if (![requestMethod isEqualToString:@"GET"]) { + return nil; + } + if (![urlPath hasPrefix:basePath]) { + return nil; + } + return [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:urlPath query:urlQuery]; + } + processBlock:^GCDWebServerResponse*(GCDWebServerRequest* request) { + GCDWebServerResponse* response = nil; + NSString* filePath = [directoryPath stringByAppendingPathComponent:GCDWebServerNormalizePath([request.path substringFromIndex:basePath.length])]; + NSString* fileType = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:NULL] fileType]; + if (fileType) { + if ([fileType isEqualToString:NSFileTypeDirectory]) { + if (indexFilename) { + NSString* indexPath = [filePath stringByAppendingPathComponent:indexFilename]; + NSString* indexType = [[[NSFileManager defaultManager] attributesOfItemAtPath:indexPath error:NULL] fileType]; + if ([indexType isEqualToString:NSFileTypeRegular]) { + return [GCDWebServerFileResponse responseWithFile:indexPath]; + } + } + response = [server _responseWithContentsOfDirectory:filePath]; + } else if ([fileType isEqualToString:NSFileTypeRegular]) { + if (allowRangeRequests) { + response = [GCDWebServerFileResponse responseWithFile:filePath byteRange:request.byteRange]; + [response setValue:@"bytes" forAdditionalHeader:@"Accept-Ranges"]; + } else { + response = [GCDWebServerFileResponse responseWithFile:filePath]; + } + } + } + if (response) { + response.cacheControlMaxAge = cacheAge; + } else { + response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_NotFound]; + } + return response; + }]; + } else { + GWS_DNOT_REACHED(); + } +} + +@end + +@implementation GCDWebServer (Logging) + ++ (void)setLogLevel:(int)level { +#if defined(__GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__) + [XLSharedFacility setMinLogLevel:level]; +#elif defined(__GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__) + GCDWebServerLogLevel = level; +#endif +} + +- (void)logVerbose:(NSString*)format, ... { + va_list arguments; + va_start(arguments, format); + GWS_LOG_VERBOSE(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); + va_end(arguments); +} + +- (void)logInfo:(NSString*)format, ... { + va_list arguments; + va_start(arguments, format); + GWS_LOG_INFO(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); + va_end(arguments); +} + +- (void)logWarning:(NSString*)format, ... { + va_list arguments; + va_start(arguments, format); + GWS_LOG_WARNING(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); + va_end(arguments); +} + +- (void)logError:(NSString*)format, ... { + va_list arguments; + va_start(arguments, format); + GWS_LOG_ERROR(@"%@", [[NSString alloc] initWithFormat:format arguments:arguments]); + va_end(arguments); +} + +@end + +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + +@implementation GCDWebServer (Testing) + +- (void)setRecordingEnabled:(BOOL)flag { + _recording = flag; +} + +- (BOOL)isRecordingEnabled { + return _recording; +} + +static CFHTTPMessageRef _CreateHTTPMessageFromData(NSData* data, BOOL isRequest) { + CFHTTPMessageRef message = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, isRequest); + if (CFHTTPMessageAppendBytes(message, data.bytes, data.length)) { + return message; + } + CFRelease(message); + return NULL; +} + +static CFHTTPMessageRef _CreateHTTPMessageFromPerformingRequest(NSData* inData, NSUInteger port) { + CFHTTPMessageRef response = NULL; + int httpSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (httpSocket > 0) { + struct sockaddr_in addr4; + bzero(&addr4, sizeof(addr4)); + addr4.sin_len = sizeof(addr4); + addr4.sin_family = AF_INET; + addr4.sin_port = htons(port); + addr4.sin_addr.s_addr = htonl(INADDR_ANY); + if (connect(httpSocket, (void*)&addr4, sizeof(addr4)) == 0) { + if (write(httpSocket, inData.bytes, inData.length) == (ssize_t)inData.length) { + NSMutableData* outData = [[NSMutableData alloc] initWithLength:(256 * 1024)]; + NSUInteger length = 0; + while (1) { + ssize_t result = read(httpSocket, (char*)outData.mutableBytes + length, outData.length - length); + if (result < 0) { + length = NSUIntegerMax; + break; + } else if (result == 0) { + break; + } + length += result; + if (length >= outData.length) { + outData.length = 2 * outData.length; + } + } + if (length != NSUIntegerMax) { + outData.length = length; + response = _CreateHTTPMessageFromData(outData, NO); + } else { + GWS_DNOT_REACHED(); + } + } + } + close(httpSocket); + } + return response; +} + +static void _LogResult(NSString* format, ...) { + va_list arguments; + va_start(arguments, format); + NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; + va_end(arguments); + fprintf(stdout, "%s\n", [message UTF8String]); +} + +- (NSInteger)runTestsWithOptions:(NSDictionary*)options inDirectory:(NSString*)path { + GWS_DCHECK([NSThread isMainThread]); + NSArray* ignoredHeaders = @[ @"Date", @"Etag" ]; // Dates are always different by definition and ETags depend on file system node IDs + NSInteger result = -1; + if ([self startWithOptions:options error:NULL]) { + _ExecuteMainThreadRunLoopSources(); + + result = 0; + NSArray* files = [[[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL] sortedArrayUsingSelector:@selector(localizedStandardCompare:)]; + for (NSString* requestFile in files) { + if (![requestFile hasSuffix:@".request"]) { + continue; + } + @autoreleasepool { + NSString* index = [[requestFile componentsSeparatedByString:@"-"] firstObject]; + BOOL success = NO; + NSData* requestData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:requestFile]]; + if (requestData) { + CFHTTPMessageRef request = _CreateHTTPMessageFromData(requestData, YES); + if (request) { + NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(request)); + NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(request)); + _LogResult(@"[%i] %@ %@", (int)[index integerValue], requestMethod, requestURL.path); + NSString* prefix = [index stringByAppendingString:@"-"]; + for (NSString* responseFile in files) { + if ([responseFile hasPrefix:prefix] && [responseFile hasSuffix:@".response"]) { + NSData* responseData = [NSData dataWithContentsOfFile:[path stringByAppendingPathComponent:responseFile]]; + if (responseData) { + CFHTTPMessageRef expectedResponse = _CreateHTTPMessageFromData(responseData, NO); + if (expectedResponse) { + CFHTTPMessageRef actualResponse = _CreateHTTPMessageFromPerformingRequest(requestData, self.port); + if (actualResponse) { + success = YES; + + CFIndex expectedStatusCode = CFHTTPMessageGetResponseStatusCode(expectedResponse); + CFIndex actualStatusCode = CFHTTPMessageGetResponseStatusCode(actualResponse); + if (actualStatusCode != expectedStatusCode) { + _LogResult(@" Status code not matching:\n Expected: %i\n Actual: %i", (int)expectedStatusCode, (int)actualStatusCode); + success = NO; + } + + NSDictionary* expectedHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(expectedResponse)); + NSDictionary* actualHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(actualResponse)); + for (NSString* expectedHeader in expectedHeaders) { + if ([ignoredHeaders containsObject:expectedHeader]) { + continue; + } + NSString* expectedValue = [expectedHeaders objectForKey:expectedHeader]; + NSString* actualValue = [actualHeaders objectForKey:expectedHeader]; + if (![actualValue isEqualToString:expectedValue]) { + _LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", expectedHeader, expectedValue, actualValue); + success = NO; + } + } + for (NSString* actualHeader in actualHeaders) { + if (![expectedHeaders objectForKey:actualHeader]) { + _LogResult(@" Header '%@' not matching:\n Expected: \"%@\"\n Actual: \"%@\"", actualHeader, nil, [actualHeaders objectForKey:actualHeader]); + success = NO; + } + } + + NSString* expectedContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(expectedResponse, CFSTR("Content-Length"))); + NSData* expectedBody = CFBridgingRelease(CFHTTPMessageCopyBody(expectedResponse)); + NSString* actualContentLength = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(actualResponse, CFSTR("Content-Length"))); + NSData* actualBody = CFBridgingRelease(CFHTTPMessageCopyBody(actualResponse)); + if ([actualContentLength isEqualToString:expectedContentLength] && (actualBody.length > expectedBody.length)) { // Handle web browser closing connection before retrieving entire body (e.g. when playing a video file) + actualBody = [actualBody subdataWithRange:NSMakeRange(0, expectedBody.length)]; + } + if ((actualBody && expectedBody && ![actualBody isEqualToData:expectedBody]) || (actualBody && !expectedBody) || (!actualBody && expectedBody)) { + _LogResult(@" Bodies not matching:\n Expected: %lu bytes\n Actual: %lu bytes", (unsigned long)expectedBody.length, (unsigned long)actualBody.length); + success = NO; +#if !TARGET_OS_IPHONE +#if DEBUG + if (GCDWebServerIsTextContentType((NSString*)[expectedHeaders objectForKey:@"Content-Type"])) { + NSString* expectedPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; + NSString* actualPath = [NSTemporaryDirectory() stringByAppendingPathComponent:(NSString*)[[[NSProcessInfo processInfo] globallyUniqueString] stringByAppendingPathExtension:@"txt"]]; + if ([expectedBody writeToFile:expectedPath atomically:YES] && [actualBody writeToFile:actualPath atomically:YES]) { + NSTask* task = [[NSTask alloc] init]; + [task setLaunchPath:@"/usr/bin/opendiff"]; + [task setArguments:@[ expectedPath, actualPath ]]; + [task launch]; + } + } +#endif +#endif + } + + CFRelease(actualResponse); + } + CFRelease(expectedResponse); + } + } else { + GWS_DNOT_REACHED(); + } + break; + } + } + CFRelease(request); + } + } else { + GWS_DNOT_REACHED(); + } + _LogResult(@""); + if (!success) { + ++result; + } + } + _ExecuteMainThreadRunLoopSources(); + } + + [self stop]; + + _ExecuteMainThreadRunLoopSources(); + } + return result; +} + +@end + +#endif diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerConnection.h b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerConnection.h new file mode 100644 index 0000000000..4d59b9f1f5 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerConnection.h @@ -0,0 +1,183 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServer.h" + +NS_ASSUME_NONNULL_BEGIN + +@class GCDWebServerHandler; + +/** + * The GCDWebServerConnection class is instantiated by GCDWebServer to handle + * each new HTTP connection. Each instance stays alive until the connection is + * closed. + * + * You cannot use this class directly, but it is made public so you can + * subclass it to override some hooks. Use the GCDWebServerOption_ConnectionClass + * option for GCDWebServer to install your custom subclass. + * + * @warning The GCDWebServerConnection retains the GCDWebServer until the + * connection is closed. + */ +@interface GCDWebServerConnection : NSObject + +/** + * Returns the GCDWebServer that owns the connection. + */ +@property(nonatomic, readonly) GCDWebServer* server; + +/** + * Returns YES if the connection is using IPv6. + */ +@property(nonatomic, readonly, getter=isUsingIPv6) BOOL usingIPv6; + +/** + * Returns the address of the local peer (i.e. server) of the connection + * as a raw "struct sockaddr". + */ +@property(nonatomic, readonly) NSData* localAddressData; + +/** + * Returns the address of the local peer (i.e. server) of the connection + * as a string. + */ +@property(nonatomic, readonly) NSString* localAddressString; + +/** + * Returns the address of the remote peer (i.e. client) of the connection + * as a raw "struct sockaddr". + */ +@property(nonatomic, readonly) NSData* remoteAddressData; + +/** + * Returns the address of the remote peer (i.e. client) of the connection + * as a string. + */ +@property(nonatomic, readonly) NSString* remoteAddressString; + +/** + * Returns the total number of bytes received from the remote peer (i.e. client) + * so far. + */ +@property(nonatomic, readonly) NSUInteger totalBytesRead; + +/** + * Returns the total number of bytes sent to the remote peer (i.e. client) so far. + */ +@property(nonatomic, readonly) NSUInteger totalBytesWritten; + +@end + +/** + * Hooks to customize the behavior of GCDWebServer HTTP connections. + * + * @warning These methods can be called on any GCD thread. + * Be sure to also call "super" when overriding them. + */ +@interface GCDWebServerConnection (Subclassing) + +/** + * This method is called when the connection is opened. + * + * Return NO to reject the connection e.g. after validating the local + * or remote address. + */ +- (BOOL)open; + +/** + * This method is called whenever data has been received + * from the remote peer (i.e. client). + * + * @warning Do not attempt to modify this data. + */ +- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length; + +/** + * This method is called whenever data has been sent + * to the remote peer (i.e. client). + * + * @warning Do not attempt to modify this data. + */ +- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length; + +/** + * This method is called after the HTTP headers have been received to + * allow replacing the request URL by another one. + * + * The default implementation returns the original URL. + */ +- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers; + +/** + * Assuming a valid HTTP request was received, this method is called before + * the request is processed. + * + * Return a non-nil GCDWebServerResponse to bypass the request processing entirely. + * + * The default implementation checks for HTTP authentication if applicable + * and returns a barebone 401 status code response if authentication failed. + */ +- (nullable GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request; + +/** + * Assuming a valid HTTP request was received and -preflightRequest: returned nil, + * this method is called to process the request by executing the handler's + * process block. + */ +- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion; + +/** + * Assuming a valid HTTP request was received and either -preflightRequest: + * or -processRequest:completion: returned a non-nil GCDWebServerResponse, + * this method is called to override the response. + * + * You can either modify the current response and return it, or return a + * completely new one. + * + * The default implementation replaces any response matching the "ETag" or + * "Last-Modified-Date" header of the request by a barebone "Not-Modified" (304) + * one. + */ +- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request; + +/** + * This method is called if any error happens while validing or processing + * the request or if no GCDWebServerResponse was generated during processing. + * + * @warning If the request was invalid (e.g. the HTTP headers were malformed), + * the "request" argument will be nil. + */ +- (void)abortRequest:(nullable GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode; + +/** + * Called when the connection is closed. + */ +- (void)close; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerConnection.m b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerConnection.m new file mode 100644 index 0000000000..b48edc61e1 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerConnection.m @@ -0,0 +1,843 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import +#import +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ +#import +#endif + +#import "GCDWebServerPrivate.h" + +#define kHeadersReadCapacity (1 * 1024) +#define kBodyReadCapacity (256 * 1024) + +typedef void (^ReadDataCompletionBlock)(BOOL success); +typedef void (^ReadHeadersCompletionBlock)(NSData* extraData); +typedef void (^ReadBodyCompletionBlock)(BOOL success); + +typedef void (^WriteDataCompletionBlock)(BOOL success); +typedef void (^WriteHeadersCompletionBlock)(BOOL success); +typedef void (^WriteBodyCompletionBlock)(BOOL success); + +static NSData* _CRLFData = nil; +static NSData* _CRLFCRLFData = nil; +static NSData* _continueData = nil; +static NSData* _lastChunkData = nil; +static NSString* _digestAuthenticationNonce = nil; +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ +static int32_t _connectionCounter = 0; +#endif + +NS_ASSUME_NONNULL_BEGIN + +@interface GCDWebServerConnection (Read) +- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block; +- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block; +- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block; +- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block; +@end + +@interface GCDWebServerConnection (Write) +- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block; +- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block; +- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block; +@end + +NS_ASSUME_NONNULL_END + +@implementation GCDWebServerConnection { + CFSocketNativeHandle _socket; + BOOL _virtualHEAD; + + CFHTTPMessageRef _requestMessage; + GCDWebServerRequest* _request; + GCDWebServerHandler* _handler; + CFHTTPMessageRef _responseMessage; + GCDWebServerResponse* _response; + NSInteger _statusCode; + + BOOL _opened; +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + NSUInteger _connectionIndex; + NSString* _requestPath; + int _requestFD; + NSString* _responsePath; + int _responseFD; +#endif +} + ++ (void)initialize { + if (_CRLFData == nil) { + _CRLFData = [[NSData alloc] initWithBytes:"\r\n" length:2]; + GWS_DCHECK(_CRLFData); + } + if (_CRLFCRLFData == nil) { + _CRLFCRLFData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; + GWS_DCHECK(_CRLFCRLFData); + } + if (_continueData == nil) { + CFHTTPMessageRef message = CFHTTPMessageCreateResponse(kCFAllocatorDefault, 100, NULL, kCFHTTPVersion1_1); + _continueData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message)); + CFRelease(message); + GWS_DCHECK(_continueData); + } + if (_lastChunkData == nil) { + _lastChunkData = [[NSData alloc] initWithBytes:"0\r\n\r\n" length:5]; + } + if (_digestAuthenticationNonce == nil) { + CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault); + _digestAuthenticationNonce = GCDWebServerComputeMD5Digest(@"%@", CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid))); + CFRelease(uuid); + } +} + +- (BOOL)isUsingIPv6 { + const struct sockaddr* localSockAddr = _localAddressData.bytes; + return (localSockAddr->sa_family == AF_INET6); +} + +- (void)_initializeResponseHeadersWithStatusCode:(NSInteger)statusCode { + _statusCode = statusCode; + _responseMessage = CFHTTPMessageCreateResponse(kCFAllocatorDefault, statusCode, NULL, kCFHTTPVersion1_1); + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Connection"), CFSTR("Close")); + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Server"), (__bridge CFStringRef)_server.serverName); + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Date"), (__bridge CFStringRef)GCDWebServerFormatRFC822([NSDate date])); +} + +- (void)_startProcessingRequest { + GWS_DCHECK(_responseMessage == NULL); + + GCDWebServerResponse* preflightResponse = [self preflightRequest:_request]; + if (preflightResponse) { + [self _finishProcessingRequest:preflightResponse]; + } else { + [self processRequest:_request + completion:^(GCDWebServerResponse* processResponse) { + [self _finishProcessingRequest:processResponse]; + }]; + } +} + +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +- (void)_finishProcessingRequest:(GCDWebServerResponse*)response { + GWS_DCHECK(_responseMessage == NULL); + BOOL hasBody = NO; + + if (response) { + response = [self overrideResponse:response forRequest:_request]; + } + if (response) { + if ([response hasBody]) { + [response prepareForReading]; + hasBody = !_virtualHEAD; + } + NSError* error = nil; + if (hasBody && ![response performOpen:&error]) { + GWS_LOG_ERROR(@"Failed opening response body for socket %i: %@", _socket, error); + } else { + _response = response; + } + } + + if (_response) { + [self _initializeResponseHeadersWithStatusCode:_response.statusCode]; + if (_response.lastModifiedDate) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Last-Modified"), (__bridge CFStringRef)GCDWebServerFormatRFC822((NSDate*)_response.lastModifiedDate)); + } + if (_response.eTag) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("ETag"), (__bridge CFStringRef)_response.eTag); + } + if ((_response.statusCode >= 200) && (_response.statusCode < 300)) { + if (_response.cacheControlMaxAge > 0) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), (__bridge CFStringRef)[NSString stringWithFormat:@"max-age=%i, public", (int)_response.cacheControlMaxAge]); + } else { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Cache-Control"), CFSTR("no-cache")); + } + } + if (_response.contentType != nil) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Type"), (__bridge CFStringRef)GCDWebServerNormalizeHeaderValue(_response.contentType)); + } + if (_response.contentLength != NSUIntegerMax) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Content-Length"), (__bridge CFStringRef)[NSString stringWithFormat:@"%lu", (unsigned long)_response.contentLength]); + } + if (_response.usesChunkedTransferEncoding) { + CFHTTPMessageSetHeaderFieldValue(_responseMessage, CFSTR("Transfer-Encoding"), CFSTR("chunked")); + } + [_response.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) { + CFHTTPMessageSetHeaderFieldValue(self->_responseMessage, (__bridge CFStringRef)key, (__bridge CFStringRef)obj); + }]; + [self writeHeadersWithCompletionBlock:^(BOOL success) { + if (success) { + if (hasBody) { + [self writeBodyWithCompletionBlock:^(BOOL successInner) { + [self->_response performClose]; // TODO: There's nothing we can do on failure as headers have already been sent + }]; + } + } else if (hasBody) { + [self->_response performClose]; + } + }]; + } else { + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } +} + +- (void)_readBodyWithLength:(NSUInteger)length initialData:(NSData*)initialData { + NSError* error = nil; + if (![_request performOpen:&error]) { + GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error); + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + return; + } + + if (initialData.length) { + if (![_request performWriteData:initialData error:&error]) { + GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error); + if (![_request performClose:&error]) { + GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); + } + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + return; + } + length -= initialData.length; + } + + if (length) { + [self readBodyWithRemainingLength:length + completionBlock:^(BOOL success) { + NSError* localError = nil; + if ([self->_request performClose:&localError]) { + [self _startProcessingRequest]; + } else { + GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } + }]; + } else { + if ([_request performClose:&error]) { + [self _startProcessingRequest]; + } else { + GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", _socket, error); + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } + } +} + +- (void)_readChunkedBodyWithInitialData:(NSData*)initialData { + NSError* error = nil; + if (![_request performOpen:&error]) { + GWS_LOG_ERROR(@"Failed opening request body for socket %i: %@", _socket, error); + [self abortRequest:_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + return; + } + + NSMutableData* chunkData = [[NSMutableData alloc] initWithData:initialData]; + [self readNextBodyChunk:chunkData + completionBlock:^(BOOL success) { + NSError* localError = nil; + if ([self->_request performClose:&localError]) { + [self _startProcessingRequest]; + } else { + GWS_LOG_ERROR(@"Failed closing request body for socket %i: %@", self->_socket, error); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } + }]; +} + +- (void)_readRequestHeaders { + _requestMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, true); + NSMutableData* headersData = [[NSMutableData alloc] initWithCapacity:kHeadersReadCapacity]; + [self readHeaders:headersData + withCompletionBlock:^(NSData* extraData) { + if (extraData) { + NSString* requestMethod = CFBridgingRelease(CFHTTPMessageCopyRequestMethod(self->_requestMessage)); // Method verbs are case-sensitive and uppercase + if (self->_server.shouldAutomaticallyMapHEADToGET && [requestMethod isEqualToString:@"HEAD"]) { + requestMethod = @"GET"; + self->_virtualHEAD = YES; + } + NSDictionary* requestHeaders = CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(self->_requestMessage)); // Header names are case-insensitive but CFHTTPMessageCopyAllHeaderFields() will standardize the common ones + NSURL* requestURL = CFBridgingRelease(CFHTTPMessageCopyRequestURL(self->_requestMessage)); + if (requestURL) { + requestURL = [self rewriteRequestURL:requestURL withMethod:requestMethod headers:requestHeaders]; + GWS_DCHECK(requestURL); + } + NSString* urlPath = requestURL ? CFBridgingRelease(CFURLCopyPath((CFURLRef)requestURL)) : nil; // Don't use -[NSURL path] which strips the ending slash + if (urlPath == nil) { + urlPath = @"/"; // CFURLCopyPath() returns NULL for a relative URL with path "//" contrary to -[NSURL path] which returns "/" + } + NSString* requestPath = urlPath ? GCDWebServerUnescapeURLString(urlPath) : nil; + NSString* queryString = requestURL ? CFBridgingRelease(CFURLCopyQueryString((CFURLRef)requestURL, NULL)) : nil; // Don't use -[NSURL query] to make sure query is not unescaped; + NSDictionary* requestQuery = queryString ? GCDWebServerParseURLEncodedForm(queryString) : @{}; + if (requestMethod && requestURL && requestHeaders && requestPath && requestQuery) { + for (self->_handler in self->_server.handlers) { + self->_request = self->_handler.matchBlock(requestMethod, requestURL, requestHeaders, requestPath, requestQuery); + if (self->_request) { + break; + } + } + if (self->_request) { + self->_request.localAddressData = self.localAddressData; + self->_request.remoteAddressData = self.remoteAddressData; + if ([self->_request hasBody]) { + [self->_request prepareForWriting]; + if (self->_request.usesChunkedTransferEncoding || (extraData.length <= self->_request.contentLength)) { + NSString* expectHeader = [requestHeaders objectForKey:@"Expect"]; + if (expectHeader) { + if ([expectHeader caseInsensitiveCompare:@"100-continue"] == NSOrderedSame) { // TODO: Actually validate request before continuing + [self writeData:_continueData + withCompletionBlock:^(BOOL success) { + if (success) { + if (self->_request.usesChunkedTransferEncoding) { + [self _readChunkedBodyWithInitialData:extraData]; + } else { + [self _readBodyWithLength:self->_request.contentLength initialData:extraData]; + } + } + }]; + } else { + GWS_LOG_ERROR(@"Unsupported 'Expect' / 'Content-Length' header combination on socket %i", self->_socket); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_ExpectationFailed]; + } + } else { + if (self->_request.usesChunkedTransferEncoding) { + [self _readChunkedBodyWithInitialData:extraData]; + } else { + [self _readBodyWithLength:self->_request.contentLength initialData:extraData]; + } + } + } else { + GWS_LOG_ERROR(@"Unexpected 'Content-Length' header value on socket %i", self->_socket); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_BadRequest]; + } + } else { + [self _startProcessingRequest]; + } + } else { + self->_request = [[GCDWebServerRequest alloc] initWithMethod:requestMethod url:requestURL headers:requestHeaders path:requestPath query:requestQuery]; + GWS_DCHECK(self->_request); + [self abortRequest:self->_request withStatusCode:kGCDWebServerHTTPStatusCode_NotImplemented]; + } + } else { + [self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + GWS_DNOT_REACHED(); + } + } else { + [self abortRequest:nil withStatusCode:kGCDWebServerHTTPStatusCode_InternalServerError]; + } + }]; +} + +- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket { + if ((self = [super init])) { + _server = server; + _localAddressData = localAddress; + _remoteAddressData = remoteAddress; + _socket = socket; + GWS_LOG_DEBUG(@"Did open connection on socket %i", _socket); + + [_server willStartConnection:self]; + + if (![self open]) { + close(_socket); + return nil; + } + _opened = YES; + + [self _readRequestHeaders]; + } + return self; +} + +- (NSString*)localAddressString { + return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES); +} + +- (NSString*)remoteAddressString { + return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES); +} + +- (void)dealloc { + int result = close(_socket); + if (result != 0) { + GWS_LOG_ERROR(@"Failed closing socket %i for connection: %s (%i)", _socket, strerror(errno), errno); + } else { + GWS_LOG_DEBUG(@"Did close connection on socket %i", _socket); + } + + if (_opened) { + [self close]; + } + + [_server didEndConnection:self]; + + if (_requestMessage) { + CFRelease(_requestMessage); + } + + if (_responseMessage) { + CFRelease(_responseMessage); + } +} + +@end + +@implementation GCDWebServerConnection (Read) + +- (void)readData:(NSMutableData*)data withLength:(NSUInteger)length completionBlock:(ReadDataCompletionBlock)block { + dispatch_read(_socket, length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t buffer, int error) { + @autoreleasepool { + if (error == 0) { + size_t size = dispatch_data_get_size(buffer); + if (size > 0) { + NSUInteger originalLength = data.length; + dispatch_data_apply(buffer, ^bool(dispatch_data_t region, size_t chunkOffset, const void* chunkBytes, size_t chunkSize) { + [data appendBytes:chunkBytes length:chunkSize]; + return true; + }); + [self didReadBytes:((char*)data.bytes + originalLength) length:(data.length - originalLength)]; + block(YES); + } else { + if (self->_totalBytesRead > 0) { + GWS_LOG_ERROR(@"No more data available on socket %i", self->_socket); + } else { + GWS_LOG_WARNING(@"No data received from socket %i", self->_socket); + } + block(NO); + } + } else { + GWS_LOG_ERROR(@"Error while reading from socket %i: %s (%i)", self->_socket, strerror(error), error); + block(NO); + } + } + }); +} + +- (void)readHeaders:(NSMutableData*)headersData withCompletionBlock:(ReadHeadersCompletionBlock)block { + GWS_DCHECK(_requestMessage); + [self readData:headersData + withLength:NSUIntegerMax + completionBlock:^(BOOL success) { + if (success) { + NSRange range = [headersData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(0, headersData.length)]; + if (range.location == NSNotFound) { + [self readHeaders:headersData withCompletionBlock:block]; + } else { + NSUInteger length = range.location + range.length; + if (CFHTTPMessageAppendBytes(self->_requestMessage, headersData.bytes, length)) { + if (CFHTTPMessageIsHeaderComplete(self->_requestMessage)) { + block([headersData subdataWithRange:NSMakeRange(length, headersData.length - length)]); + } else { + GWS_LOG_ERROR(@"Failed parsing request headers from socket %i", self->_socket); + block(nil); + } + } else { + GWS_LOG_ERROR(@"Failed appending request headers data from socket %i", self->_socket); + block(nil); + } + } + } else { + block(nil); + } + }]; +} + +- (void)readBodyWithRemainingLength:(NSUInteger)length completionBlock:(ReadBodyCompletionBlock)block { + GWS_DCHECK([_request hasBody] && ![_request usesChunkedTransferEncoding]); + NSMutableData* bodyData = [[NSMutableData alloc] initWithCapacity:kBodyReadCapacity]; + [self readData:bodyData + withLength:length + completionBlock:^(BOOL success) { + if (success) { + if (bodyData.length <= length) { + NSError* error = nil; + if ([self->_request performWriteData:bodyData error:&error]) { + NSUInteger remainingLength = length - bodyData.length; + if (remainingLength) { + [self readBodyWithRemainingLength:remainingLength completionBlock:block]; + } else { + block(YES); + } + } else { + GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", self->_socket, error); + block(NO); + } + } else { + GWS_LOG_ERROR(@"Unexpected extra content reading request body on socket %i", self->_socket); + block(NO); + GWS_DNOT_REACHED(); + } + } else { + block(NO); + } + }]; +} + +static inline NSUInteger _ScanHexNumber(const void* bytes, NSUInteger size) { + char buffer[size + 1]; + bcopy(bytes, buffer, size); + buffer[size] = 0; + char* end = NULL; + long result = strtol(buffer, &end, 16); + return ((end != NULL) && (*end == 0) && (result >= 0) ? result : NSNotFound); +} + +- (void)readNextBodyChunk:(NSMutableData*)chunkData completionBlock:(ReadBodyCompletionBlock)block { + GWS_DCHECK([_request hasBody] && [_request usesChunkedTransferEncoding]); + + while (1) { + NSRange range = [chunkData rangeOfData:_CRLFData options:0 range:NSMakeRange(0, chunkData.length)]; + if (range.location == NSNotFound) { + break; + } + NSRange extensionRange = [chunkData rangeOfData:[NSData dataWithBytes:";" length:1] options:0 range:NSMakeRange(0, range.location)]; // Ignore chunk extensions + NSUInteger length = _ScanHexNumber((char*)chunkData.bytes, extensionRange.location != NSNotFound ? extensionRange.location : range.location); + if (length != NSNotFound) { + if (length) { + if (chunkData.length < range.location + range.length + length + 2) { + break; + } + const char* ptr = (char*)chunkData.bytes + range.location + range.length + length; + if ((*ptr == '\r') && (*(ptr + 1) == '\n')) { + NSError* error = nil; + if ([_request performWriteData:[chunkData subdataWithRange:NSMakeRange(range.location + range.length, length)] error:&error]) { + [chunkData replaceBytesInRange:NSMakeRange(0, range.location + range.length + length + 2) withBytes:NULL length:0]; + } else { + GWS_LOG_ERROR(@"Failed writing request body on socket %i: %@", _socket, error); + block(NO); + return; + } + } else { + GWS_LOG_ERROR(@"Missing terminating CRLF sequence for chunk reading request body on socket %i", _socket); + block(NO); + return; + } + } else { + NSRange trailerRange = [chunkData rangeOfData:_CRLFCRLFData options:0 range:NSMakeRange(range.location, chunkData.length - range.location)]; // Ignore trailers + if (trailerRange.location != NSNotFound) { + block(YES); + return; + } + } + } else { + GWS_LOG_ERROR(@"Invalid chunk length reading request body on socket %i", _socket); + block(NO); + return; + } + } + + [self readData:chunkData + withLength:NSUIntegerMax + completionBlock:^(BOOL success) { + if (success) { + [self readNextBodyChunk:chunkData completionBlock:block]; + } else { + block(NO); + } + }]; +} + +@end + +@implementation GCDWebServerConnection (Write) + +- (void)writeData:(NSData*)data withCompletionBlock:(WriteDataCompletionBlock)block { + dispatch_data_t buffer = dispatch_data_create(data.bytes, data.length, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^{ + [data self]; // Keeps ARC from releasing data too early + }); + dispatch_write(_socket, buffer, dispatch_get_global_queue(_server.dispatchQueuePriority, 0), ^(dispatch_data_t remainingData, int error) { + @autoreleasepool { + if (error == 0) { + GWS_DCHECK(remainingData == NULL); + [self didWriteBytes:data.bytes length:data.length]; + block(YES); + } else { + GWS_LOG_ERROR(@"Error while writing to socket %i: %s (%i)", self->_socket, strerror(error), error); + block(NO); + } + } + }); +#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE + dispatch_release(buffer); +#endif +} + +- (void)writeHeadersWithCompletionBlock:(WriteHeadersCompletionBlock)block { + GWS_DCHECK(_responseMessage); + CFDataRef data = CFHTTPMessageCopySerializedMessage(_responseMessage); + [self writeData:(__bridge NSData*)data withCompletionBlock:block]; + CFRelease(data); +} + +- (void)writeBodyWithCompletionBlock:(WriteBodyCompletionBlock)block { + GWS_DCHECK([_response hasBody]); + [_response performReadDataWithCompletion:^(NSData* data, NSError* error) { + if (data) { + if (data.length) { + if (self->_response.usesChunkedTransferEncoding) { + const char* hexString = [[NSString stringWithFormat:@"%lx", (unsigned long)data.length] UTF8String]; + size_t hexLength = strlen(hexString); + NSData* chunk = [NSMutableData dataWithLength:(hexLength + 2 + data.length + 2)]; + if (chunk == nil) { + GWS_LOG_ERROR(@"Failed allocating memory for response body chunk for socket %i: %@", self->_socket, error); + block(NO); + return; + } + char* ptr = (char*)[(NSMutableData*)chunk mutableBytes]; + bcopy(hexString, ptr, hexLength); + ptr += hexLength; + *ptr++ = '\r'; + *ptr++ = '\n'; + bcopy(data.bytes, ptr, data.length); + ptr += data.length; + *ptr++ = '\r'; + *ptr = '\n'; + data = chunk; + } + [self writeData:data + withCompletionBlock:^(BOOL success) { + if (success) { + [self writeBodyWithCompletionBlock:block]; + } else { + block(NO); + } + }]; + } else { + if (self->_response.usesChunkedTransferEncoding) { + [self writeData:_lastChunkData + withCompletionBlock:^(BOOL success) { + block(success); + }]; + } else { + block(YES); + } + } + } else { + GWS_LOG_ERROR(@"Failed reading response body for socket %i: %@", self->_socket, error); + block(NO); + } + }]; +} + +@end + +@implementation GCDWebServerConnection (Subclassing) + +- (BOOL)open { +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + if (_server.recordingEnabled) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + _connectionIndex = OSAtomicIncrement32(&_connectionCounter); +#pragma clang diagnostic pop + + _requestPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + _requestFD = open([_requestPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + GWS_DCHECK(_requestFD > 0); + + _responsePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + _responseFD = open([_responsePath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + GWS_DCHECK(_responseFD > 0); + } +#endif + + return YES; +} + +- (void)didReadBytes:(const void*)bytes length:(NSUInteger)length { + GWS_LOG_DEBUG(@"Connection received %lu bytes on socket %i", (unsigned long)length, _socket); + _totalBytesRead += length; + +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + if ((_requestFD > 0) && (write(_requestFD, bytes, length) != (ssize_t)length)) { + GWS_LOG_ERROR(@"Failed recording request data: %s (%i)", strerror(errno), errno); + close(_requestFD); + _requestFD = 0; + } +#endif +} + +- (void)didWriteBytes:(const void*)bytes length:(NSUInteger)length { + GWS_LOG_DEBUG(@"Connection sent %lu bytes on socket %i", (unsigned long)length, _socket); + _totalBytesWritten += length; + +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + if ((_responseFD > 0) && (write(_responseFD, bytes, length) != (ssize_t)length)) { + GWS_LOG_ERROR(@"Failed recording response data: %s (%i)", strerror(errno), errno); + close(_responseFD); + _responseFD = 0; + } +#endif +} + +- (NSURL*)rewriteRequestURL:(NSURL*)url withMethod:(NSString*)method headers:(NSDictionary*)headers { + return url; +} + +// https://tools.ietf.org/html/rfc2617 +- (GCDWebServerResponse*)preflightRequest:(GCDWebServerRequest*)request { + GWS_LOG_DEBUG(@"Connection on socket %i preflighting request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead); + GCDWebServerResponse* response = nil; + if (_server.authenticationBasicAccounts) { + __block BOOL authenticated = NO; + NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"]; + if ([authorizationHeader hasPrefix:@"Basic "]) { + NSString* basicAccount = [authorizationHeader substringFromIndex:6]; + [_server.authenticationBasicAccounts enumerateKeysAndObjectsUsingBlock:^(NSString* username, NSString* digest, BOOL* stop) { + if ([basicAccount isEqualToString:digest]) { + authenticated = YES; + *stop = YES; + } + }]; + } + if (!authenticated) { + response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized]; + [response setValue:[NSString stringWithFormat:@"Basic realm=\"%@\"", _server.authenticationRealm] forAdditionalHeader:@"WWW-Authenticate"]; + } + } else if (_server.authenticationDigestAccounts) { + BOOL authenticated = NO; + BOOL isStaled = NO; + NSString* authorizationHeader = [request.headers objectForKey:@"Authorization"]; + if ([authorizationHeader hasPrefix:@"Digest "]) { + NSString* realm = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"realm"); + if (realm && [_server.authenticationRealm isEqualToString:realm]) { + NSString* nonce = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"nonce"); + if ([nonce isEqualToString:_digestAuthenticationNonce]) { + NSString* username = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"username"); + NSString* uri = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"uri"); + NSString* actualResponse = GCDWebServerExtractHeaderValueParameter(authorizationHeader, @"response"); + NSString* ha1 = [_server.authenticationDigestAccounts objectForKey:username]; + NSString* ha2 = GCDWebServerComputeMD5Digest(@"%@:%@", request.method, uri); // We cannot use "request.path" as the query string is required + NSString* expectedResponse = GCDWebServerComputeMD5Digest(@"%@:%@:%@", ha1, _digestAuthenticationNonce, ha2); + if ([actualResponse isEqualToString:expectedResponse]) { + authenticated = YES; + } + } else if (nonce.length) { + isStaled = YES; + } + } + } + if (!authenticated) { + response = [GCDWebServerResponse responseWithStatusCode:kGCDWebServerHTTPStatusCode_Unauthorized]; + [response setValue:[NSString stringWithFormat:@"Digest realm=\"%@\", nonce=\"%@\"%@", _server.authenticationRealm, _digestAuthenticationNonce, isStaled ? @", stale=TRUE" : @""] forAdditionalHeader:@"WWW-Authenticate"]; // TODO: Support Quality of Protection ("qop") + } + } + return response; +} + +- (void)processRequest:(GCDWebServerRequest*)request completion:(GCDWebServerCompletionBlock)completion { + GWS_LOG_DEBUG(@"Connection on socket %i processing request \"%@ %@\" with %lu bytes body", _socket, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead); + _handler.asyncProcessBlock(request, [completion copy]); +} + +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 +static inline BOOL _CompareResources(NSString* responseETag, NSString* requestETag, NSDate* responseLastModified, NSDate* requestLastModified) { + if (requestLastModified && responseLastModified) { + if ([responseLastModified compare:requestLastModified] != NSOrderedDescending) { + return YES; + } + } + if (requestETag && responseETag) { // Per the specs "If-None-Match" must be checked after "If-Modified-Since" + if ([requestETag isEqualToString:@"*"]) { + return YES; + } + if ([responseETag isEqualToString:requestETag]) { + return YES; + } + } + return NO; +} + +- (GCDWebServerResponse*)overrideResponse:(GCDWebServerResponse*)response forRequest:(GCDWebServerRequest*)request { + if ((response.statusCode >= 200) && (response.statusCode < 300) && _CompareResources(response.eTag, request.ifNoneMatch, response.lastModifiedDate, request.ifModifiedSince)) { + NSInteger code = [request.method isEqualToString:@"HEAD"] || [request.method isEqualToString:@"GET"] ? kGCDWebServerHTTPStatusCode_NotModified : kGCDWebServerHTTPStatusCode_PreconditionFailed; + GCDWebServerResponse* newResponse = [GCDWebServerResponse responseWithStatusCode:code]; + newResponse.cacheControlMaxAge = response.cacheControlMaxAge; + newResponse.lastModifiedDate = response.lastModifiedDate; + newResponse.eTag = response.eTag; + GWS_DCHECK(newResponse); + return newResponse; + } + return response; +} + +- (void)abortRequest:(GCDWebServerRequest*)request withStatusCode:(NSInteger)statusCode { + GWS_DCHECK(_responseMessage == NULL); + GWS_DCHECK((statusCode >= 400) && (statusCode < 600)); + [self _initializeResponseHeadersWithStatusCode:statusCode]; + [self writeHeadersWithCompletionBlock:^(BOOL success) { + ; // Nothing more to do + }]; + GWS_LOG_DEBUG(@"Connection aborted with status code %i on socket %i", (int)statusCode, _socket); +} + +- (void)close { +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + if (_requestPath) { + BOOL success = NO; + NSError* error = nil; + if (_requestFD > 0) { + close(_requestFD); + NSString* name = [NSString stringWithFormat:@"%03lu-%@.request", (unsigned long)_connectionIndex, _virtualHEAD ? @"HEAD" : _request.method]; + success = [[NSFileManager defaultManager] moveItemAtPath:_requestPath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error]; + } + if (!success) { + GWS_LOG_ERROR(@"Failed saving recorded request: %@", error); + GWS_DNOT_REACHED(); + } + unlink([_requestPath fileSystemRepresentation]); + } + + if (_responsePath) { + BOOL success = NO; + NSError* error = nil; + if (_responseFD > 0) { + close(_responseFD); + NSString* name = [NSString stringWithFormat:@"%03lu-%i.response", (unsigned long)_connectionIndex, (int)_statusCode]; + success = [[NSFileManager defaultManager] moveItemAtPath:_responsePath toPath:[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingPathComponent:name] error:&error]; + } + if (!success) { + GWS_LOG_ERROR(@"Failed saving recorded response: %@", error); + GWS_DNOT_REACHED(); + } + unlink([_responsePath fileSystemRepresentation]); + } +#endif + + if (_request) { + GWS_LOG_VERBOSE(@"[%@] %@ %i \"%@ %@\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, _virtualHEAD ? @"HEAD" : _request.method, _request.path, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten); + } else { + GWS_LOG_VERBOSE(@"[%@] %@ %i \"(invalid request)\" (%lu | %lu)", self.localAddressString, self.remoteAddressString, (int)_statusCode, (unsigned long)_totalBytesRead, (unsigned long)_totalBytesWritten); + } +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerFunctions.h b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerFunctions.h new file mode 100644 index 0000000000..217fb14316 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerFunctions.h @@ -0,0 +1,114 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Converts a file extension to the corresponding MIME type. + * If there is no match, "application/octet-stream" is returned. + * + * Overrides allow to customize the built-in mapping from extensions to MIME + * types. Keys of the dictionary must be lowercased file extensions without + * the period, and the values must be the corresponding MIME types. + */ +NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* _Nullable overrides); + +/** + * Add percent-escapes to a string so it can be used in a URL. + * The legal characters ":@/?&=+" are also escaped to ensure compatibility + * with URL encoded forms and URL queries. + */ +NSString* _Nullable GCDWebServerEscapeURLString(NSString* string); + +/** + * Unescapes a URL percent-encoded string. + */ +NSString* _Nullable GCDWebServerUnescapeURLString(NSString* string); + +/** + * Extracts the unescaped names and values from an + * "application/x-www-form-urlencoded" form. + * http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.1 + */ +NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form); + +/** + * On OS X, returns the IPv4 or IPv6 address as a string of the primary + * connected service or nil if not available. + * + * On iOS, returns the IPv4 or IPv6 address as a string of the WiFi + * interface if connected or nil otherwise. + */ +NSString* _Nullable GCDWebServerGetPrimaryIPAddress(BOOL useIPv6); + +/** + * Converts a date into a string using RFC822 formatting. + * https://tools.ietf.org/html/rfc822#section-5 + * https://tools.ietf.org/html/rfc1123#section-5.2.14 + */ +NSString* GCDWebServerFormatRFC822(NSDate* date); + +/** + * Converts a RFC822 formatted string into a date. + * https://tools.ietf.org/html/rfc822#section-5 + * https://tools.ietf.org/html/rfc1123#section-5.2.14 + * + * @warning Timezones other than GMT are not supported by this function. + */ +NSDate* _Nullable GCDWebServerParseRFC822(NSString* string); + +/** + * Converts a date into a string using IOS 8601 formatting. + * http://tools.ietf.org/html/rfc3339#section-5.6 + */ +NSString* GCDWebServerFormatISO8601(NSDate* date); + +/** + * Converts a ISO 8601 formatted string into a date. + * http://tools.ietf.org/html/rfc3339#section-5.6 + * + * @warning Only "calendar" variant is supported at this time and timezones + * other than GMT are not supported either. + */ +NSDate* _Nullable GCDWebServerParseISO8601(NSString* string); + +/** + * Removes "//", "/./" and "/../" components from path as well as any trailing slash. + */ +NSString* GCDWebServerNormalizePath(NSString* path); + +#ifdef __cplusplus +} +#endif + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerFunctions.m b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerFunctions.m new file mode 100644 index 0000000000..7787884d51 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerFunctions.m @@ -0,0 +1,331 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import +#if TARGET_OS_IPHONE +#import +#else +#import +#endif +#import + +#import +#import +#import + +#import "GCDWebServerPrivate.h" + +static NSDateFormatter* _dateFormatterRFC822 = nil; +static NSDateFormatter* _dateFormatterISO8601 = nil; +static dispatch_queue_t _dateFormatterQueue = NULL; + +// TODO: Handle RFC 850 and ANSI C's asctime() format +void GCDWebServerInitializeFunctions() { + GWS_DCHECK([NSThread isMainThread]); // NSDateFormatter should be initialized on main thread + if (_dateFormatterRFC822 == nil) { + _dateFormatterRFC822 = [[NSDateFormatter alloc] init]; + _dateFormatterRFC822.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + _dateFormatterRFC822.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'"; + _dateFormatterRFC822.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + GWS_DCHECK(_dateFormatterRFC822); + } + if (_dateFormatterISO8601 == nil) { + _dateFormatterISO8601 = [[NSDateFormatter alloc] init]; + _dateFormatterISO8601.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"]; + _dateFormatterISO8601.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'+00:00'"; + _dateFormatterISO8601.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; + GWS_DCHECK(_dateFormatterISO8601); + } + if (_dateFormatterQueue == NULL) { + _dateFormatterQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); + GWS_DCHECK(_dateFormatterQueue); + } +} + +NSString* GCDWebServerNormalizeHeaderValue(NSString* value) { + if (value) { + NSRange range = [value rangeOfString:@";"]; // Assume part before ";" separator is case-insensitive + if (range.location != NSNotFound) { + value = [[[value substringToIndex:range.location] lowercaseString] stringByAppendingString:[value substringFromIndex:range.location]]; + } else { + value = [value lowercaseString]; + } + } + return value; +} + +NSString* GCDWebServerTruncateHeaderValue(NSString* value) { + if (value) { + NSRange range = [value rangeOfString:@";"]; + if (range.location != NSNotFound) { + return [value substringToIndex:range.location]; + } + } + return value; +} + +NSString* GCDWebServerExtractHeaderValueParameter(NSString* value, NSString* name) { + NSString* parameter = nil; + if (value) { + NSScanner* scanner = [[NSScanner alloc] initWithString:value]; + [scanner setCaseSensitive:NO]; // Assume parameter names are case-insensitive + NSString* string = [NSString stringWithFormat:@"%@=", name]; + if ([scanner scanUpToString:string intoString:NULL]) { + [scanner scanString:string intoString:NULL]; + if ([scanner scanString:@"\"" intoString:NULL]) { + [scanner scanUpToString:@"\"" intoString:¶meter]; + } else { + [scanner scanUpToCharactersFromSet:[NSCharacterSet whitespaceCharacterSet] intoString:¶meter]; + } + } + } + return parameter; +} + +// http://www.w3schools.com/tags/ref_charactersets.asp +NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset) { + NSStringEncoding encoding = kCFStringEncodingInvalidId; + if (charset) { + encoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)charset)); + } + return (encoding != kCFStringEncodingInvalidId ? encoding : NSUTF8StringEncoding); +} + +NSString* GCDWebServerFormatRFC822(NSDate* date) { + __block NSString* string; + dispatch_sync(_dateFormatterQueue, ^{ + string = [_dateFormatterRFC822 stringFromDate:date]; + }); + return string; +} + +NSDate* GCDWebServerParseRFC822(NSString* string) { + __block NSDate* date; + dispatch_sync(_dateFormatterQueue, ^{ + date = [_dateFormatterRFC822 dateFromString:string]; + }); + return date; +} + +NSString* GCDWebServerFormatISO8601(NSDate* date) { + __block NSString* string; + dispatch_sync(_dateFormatterQueue, ^{ + string = [_dateFormatterISO8601 stringFromDate:date]; + }); + return string; +} + +NSDate* GCDWebServerParseISO8601(NSString* string) { + __block NSDate* date; + dispatch_sync(_dateFormatterQueue, ^{ + date = [_dateFormatterISO8601 dateFromString:string]; + }); + return date; +} + +BOOL GCDWebServerIsTextContentType(NSString* type) { + return ([type hasPrefix:@"text/"] || [type hasPrefix:@"application/json"] || [type hasPrefix:@"application/xml"]); +} + +NSString* GCDWebServerDescribeData(NSData* data, NSString* type) { + if (GCDWebServerIsTextContentType(type)) { + NSString* charset = GCDWebServerExtractHeaderValueParameter(type, @"charset"); + NSString* string = [[NSString alloc] initWithData:data encoding:GCDWebServerStringEncodingFromCharset(charset)]; + if (string) { + return string; + } + } + return [NSString stringWithFormat:@"<%lu bytes>", (unsigned long)data.length]; +} + +NSString* GCDWebServerGetMimeTypeForExtension(NSString* extension, NSDictionary* overrides) { + NSDictionary* builtInOverrides = @{@"css" : @"text/css"}; + NSString* mimeType = nil; + extension = [extension lowercaseString]; + if (extension.length) { + mimeType = [overrides objectForKey:extension]; + if (mimeType == nil) { + mimeType = [builtInOverrides objectForKey:extension]; + } + if (mimeType == nil) { + CFStringRef uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); + if (uti) { + mimeType = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)); + CFRelease(uti); + } + } + } + return mimeType ? mimeType : kGCDWebServerDefaultMimeType; +} + +NSString* GCDWebServerEscapeURLString(NSString* string) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, CFSTR(":@/?&=+"), kCFStringEncodingUTF8)); +#pragma clang diagnostic pop +} + +NSString* GCDWebServerUnescapeURLString(NSString* string) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + return CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault, (CFStringRef)string, CFSTR(""), kCFStringEncodingUTF8)); +#pragma clang diagnostic pop +} + +NSDictionary* GCDWebServerParseURLEncodedForm(NSString* form) { + NSMutableDictionary* parameters = [NSMutableDictionary dictionary]; + NSScanner* scanner = [[NSScanner alloc] initWithString:form]; + [scanner setCharactersToBeSkipped:nil]; + while (1) { + NSString* key = nil; + if (![scanner scanUpToString:@"=" intoString:&key] || [scanner isAtEnd]) { + break; + } + [scanner setScanLocation:([scanner scanLocation] + 1)]; + + NSString* value = nil; + [scanner scanUpToString:@"&" intoString:&value]; + if (value == nil) { + value = @""; + } + + key = [key stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + NSString* unescapedKey = key ? GCDWebServerUnescapeURLString(key) : nil; + value = [value stringByReplacingOccurrencesOfString:@"+" withString:@" "]; + NSString* unescapedValue = value ? GCDWebServerUnescapeURLString(value) : nil; + if (unescapedKey && unescapedValue) { + [parameters setObject:unescapedValue forKey:unescapedKey]; + } else { + GWS_LOG_WARNING(@"Failed parsing URL encoded form for key \"%@\" and value \"%@\"", key, value); + GWS_DNOT_REACHED(); + } + + if ([scanner isAtEnd]) { + break; + } + [scanner setScanLocation:([scanner scanLocation] + 1)]; + } + return parameters; +} + +NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService) { + char hostBuffer[NI_MAXHOST]; + char serviceBuffer[NI_MAXSERV]; + if (getnameinfo(addr, addr->sa_len, hostBuffer, sizeof(hostBuffer), serviceBuffer, sizeof(serviceBuffer), NI_NUMERICHOST | NI_NUMERICSERV | NI_NOFQDN) != 0) { +#if DEBUG + GWS_DNOT_REACHED(); +#else + return @""; +#endif + } + return includeService ? [NSString stringWithFormat:@"%s:%s", hostBuffer, serviceBuffer] : (NSString*)[NSString stringWithUTF8String:hostBuffer]; +} + +NSString* GCDWebServerGetPrimaryIPAddress(BOOL useIPv6) { + NSString* address = nil; +#if TARGET_OS_IPHONE +#if !TARGET_IPHONE_SIMULATOR && !TARGET_OS_TV + const char* primaryInterface = "en0"; // WiFi interface on iOS +#endif +#else + const char* primaryInterface = NULL; + SCDynamicStoreRef store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("GCDWebServer"), NULL, NULL); + if (store) { + CFPropertyListRef info = SCDynamicStoreCopyValue(store, CFSTR("State:/Network/Global/IPv4")); // There is no equivalent for IPv6 but the primary interface should be the same + if (info) { + NSString* interface = [(__bridge NSDictionary*)info objectForKey:@"PrimaryInterface"]; + if (interface) { + primaryInterface = [[NSString stringWithString:interface] UTF8String]; // Copy string to auto-release pool + } + CFRelease(info); + } + CFRelease(store); + } + if (primaryInterface == NULL) { + primaryInterface = "lo0"; + } +#endif + struct ifaddrs* list; + if (getifaddrs(&list) >= 0) { + for (struct ifaddrs* ifap = list; ifap; ifap = ifap->ifa_next) { +#if TARGET_IPHONE_SIMULATOR || TARGET_OS_TV + // Assume en0 is Ethernet and en1 is WiFi since there is no way to use SystemConfiguration framework in iOS Simulator + // Assumption holds for Apple TV running tvOS + if (strcmp(ifap->ifa_name, "en0") && strcmp(ifap->ifa_name, "en1")) +#else + if (strcmp(ifap->ifa_name, primaryInterface)) +#endif + { + continue; + } + if ((ifap->ifa_flags & IFF_UP) && ((!useIPv6 && (ifap->ifa_addr->sa_family == AF_INET)) || (useIPv6 && (ifap->ifa_addr->sa_family == AF_INET6)))) { + address = GCDWebServerStringFromSockAddr(ifap->ifa_addr, NO); + break; + } + } + freeifaddrs(list); + } + return address; +} + +NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) { + va_list arguments; + va_start(arguments, format); + const char* string = [[[NSString alloc] initWithFormat:format arguments:arguments] UTF8String]; + va_end(arguments); + unsigned char md5[CC_MD5_DIGEST_LENGTH]; + CC_MD5(string, (CC_LONG)strlen(string), md5); + char buffer[2 * CC_MD5_DIGEST_LENGTH + 1]; + for (int i = 0; i < CC_MD5_DIGEST_LENGTH; ++i) { + unsigned char byte = md5[i]; + unsigned char byteHi = (byte & 0xF0) >> 4; + buffer[2 * i + 0] = byteHi >= 10 ? 'a' + byteHi - 10 : '0' + byteHi; + unsigned char byteLo = byte & 0x0F; + buffer[2 * i + 1] = byteLo >= 10 ? 'a' + byteLo - 10 : '0' + byteLo; + } + buffer[2 * CC_MD5_DIGEST_LENGTH] = 0; + return (NSString*)[NSString stringWithUTF8String:buffer]; +} + +NSString* GCDWebServerNormalizePath(NSString* path) { + NSMutableArray* components = [[NSMutableArray alloc] init]; + for (NSString* component in [path componentsSeparatedByString:@"/"]) { + if ([component isEqualToString:@".."]) { + [components removeLastObject]; + } else if (component.length && ![component isEqualToString:@"."]) { + [components addObject:component]; + } + } + if (path.length && ([path characterAtIndex:0] == '/')) { + return [@"/" stringByAppendingString:[components componentsJoinedByString:@"/"]]; // Preserve initial slash + } + return [components componentsJoinedByString:@"/"]; +} diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h new file mode 100644 index 0000000000..12b697e375 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerHTTPStatusCodes.h @@ -0,0 +1,116 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html +// http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml + +#import + +/** + * Convenience constants for "informational" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerInformationalHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_Continue = 100, + kGCDWebServerHTTPStatusCode_SwitchingProtocols = 101, + kGCDWebServerHTTPStatusCode_Processing = 102 +}; + +/** + * Convenience constants for "successful" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerSuccessfulHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_OK = 200, + kGCDWebServerHTTPStatusCode_Created = 201, + kGCDWebServerHTTPStatusCode_Accepted = 202, + kGCDWebServerHTTPStatusCode_NonAuthoritativeInformation = 203, + kGCDWebServerHTTPStatusCode_NoContent = 204, + kGCDWebServerHTTPStatusCode_ResetContent = 205, + kGCDWebServerHTTPStatusCode_PartialContent = 206, + kGCDWebServerHTTPStatusCode_MultiStatus = 207, + kGCDWebServerHTTPStatusCode_AlreadyReported = 208 +}; + +/** + * Convenience constants for "redirection" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerRedirectionHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_MultipleChoices = 300, + kGCDWebServerHTTPStatusCode_MovedPermanently = 301, + kGCDWebServerHTTPStatusCode_Found = 302, + kGCDWebServerHTTPStatusCode_SeeOther = 303, + kGCDWebServerHTTPStatusCode_NotModified = 304, + kGCDWebServerHTTPStatusCode_UseProxy = 305, + kGCDWebServerHTTPStatusCode_TemporaryRedirect = 307, + kGCDWebServerHTTPStatusCode_PermanentRedirect = 308 +}; + +/** + * Convenience constants for "client error" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerClientErrorHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_BadRequest = 400, + kGCDWebServerHTTPStatusCode_Unauthorized = 401, + kGCDWebServerHTTPStatusCode_PaymentRequired = 402, + kGCDWebServerHTTPStatusCode_Forbidden = 403, + kGCDWebServerHTTPStatusCode_NotFound = 404, + kGCDWebServerHTTPStatusCode_MethodNotAllowed = 405, + kGCDWebServerHTTPStatusCode_NotAcceptable = 406, + kGCDWebServerHTTPStatusCode_ProxyAuthenticationRequired = 407, + kGCDWebServerHTTPStatusCode_RequestTimeout = 408, + kGCDWebServerHTTPStatusCode_Conflict = 409, + kGCDWebServerHTTPStatusCode_Gone = 410, + kGCDWebServerHTTPStatusCode_LengthRequired = 411, + kGCDWebServerHTTPStatusCode_PreconditionFailed = 412, + kGCDWebServerHTTPStatusCode_RequestEntityTooLarge = 413, + kGCDWebServerHTTPStatusCode_RequestURITooLong = 414, + kGCDWebServerHTTPStatusCode_UnsupportedMediaType = 415, + kGCDWebServerHTTPStatusCode_RequestedRangeNotSatisfiable = 416, + kGCDWebServerHTTPStatusCode_ExpectationFailed = 417, + kGCDWebServerHTTPStatusCode_UnprocessableEntity = 422, + kGCDWebServerHTTPStatusCode_Locked = 423, + kGCDWebServerHTTPStatusCode_FailedDependency = 424, + kGCDWebServerHTTPStatusCode_UpgradeRequired = 426, + kGCDWebServerHTTPStatusCode_PreconditionRequired = 428, + kGCDWebServerHTTPStatusCode_TooManyRequests = 429, + kGCDWebServerHTTPStatusCode_RequestHeaderFieldsTooLarge = 431 +}; + +/** + * Convenience constants for "server error" HTTP status codes. + */ +typedef NS_ENUM(NSInteger, GCDWebServerServerErrorHTTPStatusCode) { + kGCDWebServerHTTPStatusCode_InternalServerError = 500, + kGCDWebServerHTTPStatusCode_NotImplemented = 501, + kGCDWebServerHTTPStatusCode_BadGateway = 502, + kGCDWebServerHTTPStatusCode_ServiceUnavailable = 503, + kGCDWebServerHTTPStatusCode_GatewayTimeout = 504, + kGCDWebServerHTTPStatusCode_HTTPVersionNotSupported = 505, + kGCDWebServerHTTPStatusCode_InsufficientStorage = 507, + kGCDWebServerHTTPStatusCode_LoopDetected = 508, + kGCDWebServerHTTPStatusCode_NotExtended = 510, + kGCDWebServerHTTPStatusCode_NetworkAuthenticationRequired = 511 +}; diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerPrivate.h b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerPrivate.h new file mode 100644 index 0000000000..4668a97875 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerPrivate.h @@ -0,0 +1,224 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import +#import + +/** + * All GCDWebServer headers. + */ + +#import "GCDWebServerHTTPStatusCodes.h" +#import "GCDWebServerFunctions.h" + +#import "GCDWebServer.h" +#import "GCDWebServerConnection.h" + +#import "GCDWebServerDataRequest.h" +#import "GCDWebServerFileRequest.h" +#import "GCDWebServerMultiPartFormRequest.h" +#import "GCDWebServerURLEncodedFormRequest.h" + +#import "GCDWebServerDataResponse.h" +#import "GCDWebServerErrorResponse.h" +#import "GCDWebServerFileResponse.h" +#import "GCDWebServerStreamedResponse.h" + +/** + * Check if a custom logging facility should be used instead. + */ + +#if defined(__GCDWEBSERVER_LOGGING_HEADER__) + +#define __GCDWEBSERVER_LOGGING_FACILITY_CUSTOM__ + +#import __GCDWEBSERVER_LOGGING_HEADER__ + +/** + * Automatically detect if XLFacility is available and if so use it as a + * logging facility. + */ + +#elif defined(__has_include) && __has_include("XLFacilityMacros.h") + +#define __GCDWEBSERVER_LOGGING_FACILITY_XLFACILITY__ + +#undef XLOG_TAG +#define XLOG_TAG @"gcdwebserver.internal" + +#import "XLFacilityMacros.h" + +#define GWS_LOG_DEBUG(...) XLOG_DEBUG(__VA_ARGS__) +#define GWS_LOG_VERBOSE(...) XLOG_VERBOSE(__VA_ARGS__) +#define GWS_LOG_INFO(...) XLOG_INFO(__VA_ARGS__) +#define GWS_LOG_WARNING(...) XLOG_WARNING(__VA_ARGS__) +#define GWS_LOG_ERROR(...) XLOG_ERROR(__VA_ARGS__) + +#define GWS_DCHECK(__CONDITION__) XLOG_DEBUG_CHECK(__CONDITION__) +#define GWS_DNOT_REACHED() XLOG_DEBUG_UNREACHABLE() + +/** + * If all of the above fail, then use GCDWebServer built-in + * logging facility. + */ + +#else + +#define __GCDWEBSERVER_LOGGING_FACILITY_BUILTIN__ + +typedef NS_ENUM(int, GCDWebServerLoggingLevel) { + kGCDWebServerLoggingLevel_Debug = 0, + kGCDWebServerLoggingLevel_Verbose, + kGCDWebServerLoggingLevel_Info, + kGCDWebServerLoggingLevel_Warning, + kGCDWebServerLoggingLevel_Error +}; + +extern GCDWebServerLoggingLevel GCDWebServerLogLevel; +extern void GCDWebServerLogMessage(GCDWebServerLoggingLevel level, NSString* _Nonnull format, ...) NS_FORMAT_FUNCTION(2, 3); + +#if DEBUG +#define GWS_LOG_DEBUG(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Debug) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Debug, __VA_ARGS__); \ + } while (0) +#else +#define GWS_LOG_DEBUG(...) +#endif +#define GWS_LOG_VERBOSE(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Verbose) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Verbose, __VA_ARGS__); \ + } while (0) +#define GWS_LOG_INFO(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Info) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Info, __VA_ARGS__); \ + } while (0) +#define GWS_LOG_WARNING(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Warning) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Warning, __VA_ARGS__); \ + } while (0) +#define GWS_LOG_ERROR(...) \ + do { \ + if (GCDWebServerLogLevel <= kGCDWebServerLoggingLevel_Error) GCDWebServerLogMessage(kGCDWebServerLoggingLevel_Error, __VA_ARGS__); \ + } while (0) + +#endif + +/** + * Consistency check macros used when building Debug only. + */ + +#if !defined(GWS_DCHECK) || !defined(GWS_DNOT_REACHED) + +#if DEBUG + +#define GWS_DCHECK(__CONDITION__) \ + do { \ + if (!(__CONDITION__)) { \ + abort(); \ + } \ + } while (0) +#define GWS_DNOT_REACHED() abort() + +#else + +#define GWS_DCHECK(__CONDITION__) +#define GWS_DNOT_REACHED() + +#endif + +#endif + +NS_ASSUME_NONNULL_BEGIN + +/** + * GCDWebServer internal constants and APIs. + */ + +#define kGCDWebServerDefaultMimeType @"application/octet-stream" +#define kGCDWebServerErrorDomain @"GCDWebServerErrorDomain" + +static inline BOOL GCDWebServerIsValidByteRange(NSRange range) { + return ((range.location != NSUIntegerMax) || (range.length > 0)); +} + +static inline NSError* GCDWebServerMakePosixError(int code) { + return [NSError errorWithDomain:NSPOSIXErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey : (NSString*)[NSString stringWithUTF8String:strerror(code)]}]; +} + +extern void GCDWebServerInitializeFunctions(void); +extern NSString* _Nullable GCDWebServerNormalizeHeaderValue(NSString* _Nullable value); +extern NSString* _Nullable GCDWebServerTruncateHeaderValue(NSString* _Nullable value); +extern NSString* _Nullable GCDWebServerExtractHeaderValueParameter(NSString* _Nullable value, NSString* attribute); +extern NSStringEncoding GCDWebServerStringEncodingFromCharset(NSString* charset); +extern BOOL GCDWebServerIsTextContentType(NSString* type); +extern NSString* GCDWebServerDescribeData(NSData* data, NSString* contentType); +extern NSString* GCDWebServerComputeMD5Digest(NSString* format, ...) NS_FORMAT_FUNCTION(1, 2); +extern NSString* GCDWebServerStringFromSockAddr(const struct sockaddr* addr, BOOL includeService); + +@interface GCDWebServerConnection () +- (instancetype)initWithServer:(GCDWebServer*)server localAddress:(NSData*)localAddress remoteAddress:(NSData*)remoteAddress socket:(CFSocketNativeHandle)socket; +@end + +@interface GCDWebServer () +@property(nonatomic, readonly) NSMutableArray* handlers; +@property(nonatomic, readonly, nullable) NSString* serverName; +@property(nonatomic, readonly, nullable) NSString* authenticationRealm; +@property(nonatomic, readonly, nullable) NSMutableDictionary* authenticationBasicAccounts; +@property(nonatomic, readonly, nullable) NSMutableDictionary* authenticationDigestAccounts; +@property(nonatomic, readonly) BOOL shouldAutomaticallyMapHEADToGET; +@property(nonatomic, readonly) dispatch_queue_priority_t dispatchQueuePriority; +- (void)willStartConnection:(GCDWebServerConnection*)connection; +- (void)didEndConnection:(GCDWebServerConnection*)connection; +@end + +@interface GCDWebServerHandler : NSObject +@property(nonatomic, readonly) GCDWebServerMatchBlock matchBlock; +@property(nonatomic, readonly) GCDWebServerAsyncProcessBlock asyncProcessBlock; +@end + +@interface GCDWebServerRequest () +@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; +@property(nonatomic) NSData* localAddressData; +@property(nonatomic) NSData* remoteAddressData; +- (void)prepareForWriting; +- (BOOL)performOpen:(NSError**)error; +- (BOOL)performWriteData:(NSData*)data error:(NSError**)error; +- (BOOL)performClose:(NSError**)error; +- (void)setAttribute:(nullable id)attribute forKey:(NSString*)key; +@end + +@interface GCDWebServerResponse () +@property(nonatomic, readonly) NSDictionary* additionalHeaders; +@property(nonatomic, readonly) BOOL usesChunkedTransferEncoding; +- (void)prepareForReading; +- (BOOL)performOpen:(NSError**)error; +- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block; +- (void)performClose; +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerRequest.h b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerRequest.h new file mode 100644 index 0000000000..79b446a5d2 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerRequest.h @@ -0,0 +1,210 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Attribute key to retrieve an NSArray containing NSStrings from a GCDWebServerRequest + * with the contents of any regular expression captures done on the request path. + * + * @warning This attribute will only be set on the request if adding a handler using + * -addHandlerForMethod:pathRegex:requestClass:processBlock:. + */ +extern NSString* const GCDWebServerRequestAttribute_RegexCaptures; + +/** + * This protocol is used by the GCDWebServerConnection to communicate with + * the GCDWebServerRequest and write the received HTTP body data. + * + * Note that multiple GCDWebServerBodyWriter objects can be chained together + * internally e.g. to automatically decode gzip encoded content before + * passing it on to the GCDWebServerRequest. + * + * @warning These methods can be called on any GCD thread. + */ +@protocol GCDWebServerBodyWriter + +/** + * This method is called before any body data is received. + * + * It should return YES on success or NO on failure and set the "error" argument + * which is guaranteed to be non-NULL. + */ +- (BOOL)open:(NSError**)error; + +/** + * This method is called whenever body data has been received. + * + * It should return YES on success or NO on failure and set the "error" argument + * which is guaranteed to be non-NULL. + */ +- (BOOL)writeData:(NSData*)data error:(NSError**)error; + +/** + * This method is called after all body data has been received. + * + * It should return YES on success or NO on failure and set the "error" argument + * which is guaranteed to be non-NULL. + */ +- (BOOL)close:(NSError**)error; + +@end + +/** + * The GCDWebServerRequest class is instantiated by the GCDWebServerConnection + * after the HTTP headers have been received. Each instance wraps a single HTTP + * request. If a body is present, the methods from the GCDWebServerBodyWriter + * protocol will be called by the GCDWebServerConnection to receive it. + * + * The default implementation of the GCDWebServerBodyWriter protocol on the class + * simply ignores the body data. + * + * @warning GCDWebServerRequest instances can be created and used on any GCD thread. + */ +@interface GCDWebServerRequest : NSObject + +/** + * Returns the HTTP method for the request. + */ +@property(nonatomic, readonly) NSString* method; + +/** + * Returns the URL for the request. + */ +@property(nonatomic, readonly) NSURL* URL; + +/** + * Returns the HTTP headers for the request. + */ +@property(nonatomic, readonly) NSDictionary* headers; + +/** + * Returns the path component of the URL for the request. + */ +@property(nonatomic, readonly) NSString* path; + +/** + * Returns the parsed and unescaped query component of the URL for the request. + * + * @warning This property will be nil if there is no query in the URL. + */ +@property(nonatomic, readonly, nullable) NSDictionary* query; + +/** + * Returns the content type for the body of the request parsed from the + * "Content-Type" header. + * + * This property will be nil if the request has no body or set to + * "application/octet-stream" if a body is present but there was no + * "Content-Type" header. + */ +@property(nonatomic, readonly, nullable) NSString* contentType; + +/** + * Returns the content length for the body of the request parsed from the + * "Content-Length" header. + * + * This property will be set to "NSUIntegerMax" if the request has no body or + * if there is a body but no "Content-Length" header, typically because + * chunked transfer encoding is used. + */ +@property(nonatomic, readonly) NSUInteger contentLength; + +/** + * Returns the parsed "If-Modified-Since" header or nil if absent or malformed. + */ +@property(nonatomic, readonly, nullable) NSDate* ifModifiedSince; + +/** + * Returns the parsed "If-None-Match" header or nil if absent or malformed. + */ +@property(nonatomic, readonly, nullable) NSString* ifNoneMatch; + +/** + * Returns the parsed "Range" header or (NSUIntegerMax, 0) if absent or malformed. + * The range will be set to (offset, length) if expressed from the beginning + * of the entity body, or (NSUIntegerMax, length) if expressed from its end. + */ +@property(nonatomic, readonly) NSRange byteRange; + +/** + * Returns YES if the client supports gzip content encoding according to the + * "Accept-Encoding" header. + */ +@property(nonatomic, readonly) BOOL acceptsGzipContentEncoding; + +/** + * Returns the address of the local peer (i.e. server) for the request + * as a raw "struct sockaddr". + */ +@property(nonatomic, readonly) NSData* localAddressData; + +/** + * Returns the address of the local peer (i.e. server) for the request + * as a string. + */ +@property(nonatomic, readonly) NSString* localAddressString; + +/** + * Returns the address of the remote peer (i.e. client) for the request + * as a raw "struct sockaddr". + */ +@property(nonatomic, readonly) NSData* remoteAddressData; + +/** + * Returns the address of the remote peer (i.e. client) for the request + * as a string. + */ +@property(nonatomic, readonly) NSString* remoteAddressString; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(nullable NSDictionary*)query; + +/** + * Convenience method that checks if the contentType property is defined. + */ +- (BOOL)hasBody; + +/** + * Convenience method that checks if the byteRange property is defined. + */ +- (BOOL)hasByteRange; + +/** + * Retrieves an attribute associated with this request using the given key. + * + * @return The attribute value for the key. + */ +- (nullable id)attributeForKey:(NSString*)key; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerRequest.m b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerRequest.m new file mode 100644 index 0000000000..5648eff7a9 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerRequest.m @@ -0,0 +1,303 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import + +#import "GCDWebServerPrivate.h" + +NSString* const GCDWebServerRequestAttribute_RegexCaptures = @"GCDWebServerRequestAttribute_RegexCaptures"; + +#define kZlibErrorDomain @"ZlibErrorDomain" +#define kGZipInitialBufferSize (256 * 1024) + +@interface GCDWebServerBodyDecoder : NSObject +@end + +@interface GCDWebServerGZipDecoder : GCDWebServerBodyDecoder +@end + +@implementation GCDWebServerBodyDecoder { + GCDWebServerRequest* __unsafe_unretained _request; + id __unsafe_unretained _writer; +} + +- (instancetype)initWithRequest:(GCDWebServerRequest* _Nonnull)request writer:(id _Nonnull)writer { + if ((self = [super init])) { + _request = request; + _writer = writer; + } + return self; +} + +- (BOOL)open:(NSError**)error { + return [_writer open:error]; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + return [_writer writeData:data error:error]; +} + +- (BOOL)close:(NSError**)error { + return [_writer close:error]; +} + +@end + +@implementation GCDWebServerGZipDecoder { + z_stream _stream; + BOOL _finished; +} + +- (BOOL)open:(NSError**)error { + int result = inflateInit2(&_stream, 15 + 16); + if (result != Z_OK) { + if (error) { + *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; + } + return NO; + } + if (![super open:error]) { + inflateEnd(&_stream); + return NO; + } + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + GWS_DCHECK(!_finished); + _stream.next_in = (Bytef*)data.bytes; + _stream.avail_in = (uInt)data.length; + NSMutableData* decodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize]; + if (decodedData == nil) { + GWS_DNOT_REACHED(); + return NO; + } + NSUInteger length = 0; + while (1) { + NSUInteger maxLength = decodedData.length - length; + _stream.next_out = (Bytef*)((char*)decodedData.mutableBytes + length); + _stream.avail_out = (uInt)maxLength; + int result = inflate(&_stream, Z_NO_FLUSH); + if ((result != Z_OK) && (result != Z_STREAM_END)) { + if (error) { + *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; + } + return NO; + } + length += maxLength - _stream.avail_out; + if (_stream.avail_out > 0) { + if (result == Z_STREAM_END) { + _finished = YES; + } + break; + } + decodedData.length = 2 * decodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available + } + decodedData.length = length; + BOOL success = length ? [super writeData:decodedData error:error] : YES; // No need to call writer if we have no data yet + return success; +} + +- (BOOL)close:(NSError**)error { + GWS_DCHECK(_finished); + inflateEnd(&_stream); + return [super close:error]; +} + +@end + +@implementation GCDWebServerRequest { + BOOL _opened; + NSMutableArray* _decoders; + id __unsafe_unretained _writer; + NSMutableDictionary* _attributes; +} + +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { + if ((self = [super init])) { + _method = [method copy]; + _URL = url; + _headers = headers; + _path = [path copy]; + _query = query; + + _contentType = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Content-Type"]); + _usesChunkedTransferEncoding = [GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Transfer-Encoding"]) isEqualToString:@"chunked"]; + NSString* lengthHeader = [_headers objectForKey:@"Content-Length"]; + if (lengthHeader) { + NSInteger length = [lengthHeader integerValue]; + if (_usesChunkedTransferEncoding || (length < 0)) { + GWS_LOG_WARNING(@"Invalid 'Content-Length' header '%@' for '%@' request on \"%@\"", lengthHeader, _method, _URL); + GWS_DNOT_REACHED(); + return nil; + } + _contentLength = length; + if (_contentType == nil) { + _contentType = kGCDWebServerDefaultMimeType; + } + } else if (_usesChunkedTransferEncoding) { + if (_contentType == nil) { + _contentType = kGCDWebServerDefaultMimeType; + } + _contentLength = NSUIntegerMax; + } else { + if (_contentType) { + GWS_LOG_WARNING(@"Ignoring 'Content-Type' header for '%@' request on \"%@\"", _method, _URL); + _contentType = nil; // Content-Type without Content-Length or chunked-encoding doesn't make sense + } + _contentLength = NSUIntegerMax; + } + + NSString* modifiedHeader = [_headers objectForKey:@"If-Modified-Since"]; + if (modifiedHeader) { + _ifModifiedSince = [GCDWebServerParseRFC822(modifiedHeader) copy]; + } + _ifNoneMatch = [_headers objectForKey:@"If-None-Match"]; + + _byteRange = NSMakeRange(NSUIntegerMax, 0); + NSString* rangeHeader = GCDWebServerNormalizeHeaderValue([_headers objectForKey:@"Range"]); + if (rangeHeader) { + if ([rangeHeader hasPrefix:@"bytes="]) { + NSArray* components = [[rangeHeader substringFromIndex:6] componentsSeparatedByString:@","]; + if (components.count == 1) { + components = [(NSString*)[components firstObject] componentsSeparatedByString:@"-"]; + if (components.count == 2) { + NSString* startString = [components objectAtIndex:0]; + NSInteger startValue = [startString integerValue]; + NSString* endString = [components objectAtIndex:1]; + NSInteger endValue = [endString integerValue]; + if (startString.length && (startValue >= 0) && endString.length && (endValue >= startValue)) { // The second 500 bytes: "500-999" + _byteRange.location = startValue; + _byteRange.length = endValue - startValue + 1; + } else if (startString.length && (startValue >= 0)) { // The bytes after 9500 bytes: "9500-" + _byteRange.location = startValue; + _byteRange.length = NSUIntegerMax; + } else if (endString.length && (endValue > 0)) { // The final 500 bytes: "-500" + _byteRange.location = NSUIntegerMax; + _byteRange.length = endValue; + } + } + } + } + if ((_byteRange.location == NSUIntegerMax) && (_byteRange.length == 0)) { // Ignore "Range" header if syntactically invalid + GWS_LOG_WARNING(@"Failed to parse 'Range' header \"%@\" for url: %@", rangeHeader, url); + } + } + + if ([[_headers objectForKey:@"Accept-Encoding"] rangeOfString:@"gzip"].location != NSNotFound) { + _acceptsGzipContentEncoding = YES; + } + + _decoders = [[NSMutableArray alloc] init]; + _attributes = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (BOOL)hasBody { + return _contentType ? YES : NO; +} + +- (BOOL)hasByteRange { + return GCDWebServerIsValidByteRange(_byteRange); +} + +- (id)attributeForKey:(NSString*)key { + return [_attributes objectForKey:key]; +} + +- (BOOL)open:(NSError**)error { + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + return YES; +} + +- (BOOL)close:(NSError**)error { + return YES; +} + +- (void)prepareForWriting { + _writer = self; + if ([GCDWebServerNormalizeHeaderValue([self.headers objectForKey:@"Content-Encoding"]) isEqualToString:@"gzip"]) { + GCDWebServerGZipDecoder* decoder = [[GCDWebServerGZipDecoder alloc] initWithRequest:self writer:_writer]; + [_decoders addObject:decoder]; + _writer = decoder; + } +} + +- (BOOL)performOpen:(NSError**)error { + GWS_DCHECK(_contentType); + GWS_DCHECK(_writer); + if (_opened) { + GWS_DNOT_REACHED(); + return NO; + } + _opened = YES; + return [_writer open:error]; +} + +- (BOOL)performWriteData:(NSData*)data error:(NSError**)error { + GWS_DCHECK(_opened); + return [_writer writeData:data error:error]; +} + +- (BOOL)performClose:(NSError**)error { + GWS_DCHECK(_opened); + return [_writer close:error]; +} + +- (void)setAttribute:(id)attribute forKey:(NSString*)key { + [_attributes setValue:attribute forKey:key]; +} + +- (NSString*)localAddressString { + return GCDWebServerStringFromSockAddr(_localAddressData.bytes, YES); +} + +- (NSString*)remoteAddressString { + return GCDWebServerStringFromSockAddr(_remoteAddressData.bytes, YES); +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithFormat:@"%@ %@", _method, _path]; + for (NSString* argument in [[_query allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [description appendFormat:@"\n %@ = %@", argument, [_query objectForKey:argument]]; + } + [description appendString:@"\n"]; + for (NSString* header in [[_headers allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [description appendFormat:@"\n%@: %@", header, [_headers objectForKey:header]]; + } + return description; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerResponse.h b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerResponse.h new file mode 100644 index 0000000000..7ef7d91735 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerResponse.h @@ -0,0 +1,212 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerBodyReaderCompletionBlock is passed by GCDWebServer to the + * GCDWebServerBodyReader object when reading data from it asynchronously. + */ +typedef void (^GCDWebServerBodyReaderCompletionBlock)(NSData* data, NSError* _Nullable error); + +/** + * This protocol is used by the GCDWebServerConnection to communicate with + * the GCDWebServerResponse and read the HTTP body data to send. + * + * Note that multiple GCDWebServerBodyReader objects can be chained together + * internally e.g. to automatically apply gzip encoding to the content before + * passing it on to the GCDWebServerResponse. + * + * @warning These methods can be called on any GCD thread. + */ +@protocol GCDWebServerBodyReader + +@required + +/** + * This method is called before any body data is sent. + * + * It should return YES on success or NO on failure and set the "error" argument + * which is guaranteed to be non-NULL. + */ +- (BOOL)open:(NSError**)error; + +/** + * This method is called whenever body data is sent. + * + * It should return a non-empty NSData if there is body data available, + * or an empty NSData there is no more body data, or nil on error and set + * the "error" argument which is guaranteed to be non-NULL. + */ +- (nullable NSData*)readData:(NSError**)error; + +/** + * This method is called after all body data has been sent. + */ +- (void)close; + +@optional + +/** + * If this method is implemented, it will be preferred over -readData:. + * + * It must call the passed block when data is available, passing a non-empty + * NSData if there is body data available, or an empty NSData there is no more + * body data, or nil on error and pass an NSError along. + */ +- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block; + +@end + +/** + * The GCDWebServerResponse class is used to wrap a single HTTP response. + * It is instantiated by the handler of the GCDWebServer that handled the request. + * If a body is present, the methods from the GCDWebServerBodyReader protocol + * will be called by the GCDWebServerConnection to send it. + * + * The default implementation of the GCDWebServerBodyReader protocol + * on the class simply returns an empty body. + * + * @warning GCDWebServerResponse instances can be created and used on any GCD thread. + */ +@interface GCDWebServerResponse : NSObject + +/** + * Sets the content type for the body of the response. + * + * The default value is nil i.e. the response has no body. + * + * @warning This property must be set if a body is present. + */ +@property(nonatomic, copy, nullable) NSString* contentType; + +/** + * Sets the content length for the body of the response. If a body is present + * but this property is set to "NSUIntegerMax", this means the length of the body + * cannot be known ahead of time. Chunked transfer encoding will be + * automatically enabled by the GCDWebServerConnection to comply with HTTP/1.1 + * specifications. + * + * The default value is "NSUIntegerMax" i.e. the response has no body or its length + * is undefined. + */ +@property(nonatomic) NSUInteger contentLength; + +/** + * Sets the HTTP status code for the response. + * + * The default value is 200 i.e. "OK". + */ +@property(nonatomic) NSInteger statusCode; + +/** + * Sets the caching hint for the response using the "Cache-Control" header. + * This value is expressed in seconds. + * + * The default value is 0 i.e. "no-cache". + */ +@property(nonatomic) NSUInteger cacheControlMaxAge; + +/** + * Sets the last modified date for the response using the "Last-Modified" header. + * + * The default value is nil. + */ +@property(nonatomic, nullable) NSDate* lastModifiedDate; + +/** + * Sets the ETag for the response using the "ETag" header. + * + * The default value is nil. + */ +@property(nonatomic, copy, nullable) NSString* eTag; + +/** + * Enables gzip encoding for the response body. + * + * The default value is NO. + * + * @warning Enabling gzip encoding will remove any "Content-Length" header + * since the length of the body is not known anymore. The client will still + * be able to determine the body length when connection is closed per + * HTTP/1.1 specifications. + */ +@property(nonatomic, getter=isGZipContentEncodingEnabled) BOOL gzipContentEncodingEnabled; + +/** + * Creates an empty response. + */ ++ (instancetype)response; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)init; + +/** + * Sets an additional HTTP header on the response. + * Pass a nil value to remove an additional header. + * + * @warning Do not attempt to override the primary headers used + * by GCDWebServerResponse like "Content-Type", "ETag", etc... + */ +- (void)setValue:(nullable NSString*)value forAdditionalHeader:(NSString*)header; + +/** + * Convenience method that checks if the contentType property is defined. + */ +- (BOOL)hasBody; + +@end + +@interface GCDWebServerResponse (Extensions) + +/** + * Creates a empty response with a specific HTTP status code. + */ ++ (instancetype)responseWithStatusCode:(NSInteger)statusCode; + +/** + * Creates an HTTP redirect response to a new URL. + */ ++ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent; + +/** + * Initializes an empty response with a specific HTTP status code. + */ +- (instancetype)initWithStatusCode:(NSInteger)statusCode; + +/** + * Initializes an HTTP redirect response to a new URL. + */ +- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerResponse.m b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerResponse.m new file mode 100644 index 0000000000..46042b21b3 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Core/GCDWebServerResponse.m @@ -0,0 +1,284 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import + +#import "GCDWebServerPrivate.h" + +#define kZlibErrorDomain @"ZlibErrorDomain" +#define kGZipInitialBufferSize (256 * 1024) + +@interface GCDWebServerBodyEncoder : NSObject +@end + +@interface GCDWebServerGZipEncoder : GCDWebServerBodyEncoder +@end + +@implementation GCDWebServerBodyEncoder { + GCDWebServerResponse* __unsafe_unretained _response; + id __unsafe_unretained _reader; +} + +- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id _Nonnull)reader { + if ((self = [super init])) { + _response = response; + _reader = reader; + } + return self; +} + +- (BOOL)open:(NSError**)error { + return [_reader open:error]; +} + +- (NSData*)readData:(NSError**)error { + return [_reader readData:error]; +} + +- (void)close { + [_reader close]; +} + +@end + +@implementation GCDWebServerGZipEncoder { + z_stream _stream; + BOOL _finished; +} + +- (instancetype)initWithResponse:(GCDWebServerResponse* _Nonnull)response reader:(id _Nonnull)reader { + if ((self = [super initWithResponse:response reader:reader])) { + response.contentLength = NSUIntegerMax; // Make sure "Content-Length" header is not set since we don't know it + [response setValue:@"gzip" forAdditionalHeader:@"Content-Encoding"]; + } + return self; +} + +- (BOOL)open:(NSError**)error { + int result = deflateInit2(&_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); + if (result != Z_OK) { + if (error) { + *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; + } + return NO; + } + if (![super open:error]) { + deflateEnd(&_stream); + return NO; + } + return YES; +} + +- (NSData*)readData:(NSError**)error { + NSMutableData* encodedData; + if (_finished) { + encodedData = [[NSMutableData alloc] init]; + } else { + encodedData = [[NSMutableData alloc] initWithLength:kGZipInitialBufferSize]; + if (encodedData == nil) { + GWS_DNOT_REACHED(); + return nil; + } + NSUInteger length = 0; + do { + NSData* data = [super readData:error]; + if (data == nil) { + return nil; + } + _stream.next_in = (Bytef*)data.bytes; + _stream.avail_in = (uInt)data.length; + while (1) { + NSUInteger maxLength = encodedData.length - length; + _stream.next_out = (Bytef*)((char*)encodedData.mutableBytes + length); + _stream.avail_out = (uInt)maxLength; + int result = deflate(&_stream, data.length ? Z_NO_FLUSH : Z_FINISH); + if (result == Z_STREAM_END) { + _finished = YES; + } else if (result != Z_OK) { + if (error) { + *error = [NSError errorWithDomain:kZlibErrorDomain code:result userInfo:nil]; + } + return nil; + } + length += maxLength - _stream.avail_out; + if (_stream.avail_out > 0) { + break; + } + encodedData.length = 2 * encodedData.length; // zlib has used all the output buffer so resize it and try again in case more data is available + } + GWS_DCHECK(_stream.avail_in == 0); + } while (length == 0); // Make sure we don't return an empty NSData if not in finished state + encodedData.length = length; + } + return encodedData; +} + +- (void)close { + deflateEnd(&_stream); + [super close]; +} + +@end + +@implementation GCDWebServerResponse { + BOOL _opened; + NSMutableArray* _encoders; + id __unsafe_unretained _reader; +} + ++ (instancetype)response { + return [(GCDWebServerResponse*)[[self class] alloc] init]; +} + +- (instancetype)init { + if ((self = [super init])) { + _contentType = nil; + _contentLength = NSUIntegerMax; + _statusCode = kGCDWebServerHTTPStatusCode_OK; + _cacheControlMaxAge = 0; + _additionalHeaders = [[NSMutableDictionary alloc] init]; + _encoders = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)setValue:(NSString*)value forAdditionalHeader:(NSString*)header { + [_additionalHeaders setValue:value forKey:header]; +} + +- (BOOL)hasBody { + return _contentType ? YES : NO; +} + +- (BOOL)usesChunkedTransferEncoding { + return (_contentType != nil) && (_contentLength == NSUIntegerMax); +} + +- (BOOL)open:(NSError**)error { + return YES; +} + +- (NSData*)readData:(NSError**)error { + return [NSData data]; +} + +- (void)close { + ; +} + +- (void)prepareForReading { + _reader = self; + if (_gzipContentEncodingEnabled) { + GCDWebServerGZipEncoder* encoder = [[GCDWebServerGZipEncoder alloc] initWithResponse:self reader:_reader]; + [_encoders addObject:encoder]; + _reader = encoder; + } +} + +- (BOOL)performOpen:(NSError**)error { + GWS_DCHECK(_contentType); + GWS_DCHECK(_reader); + if (_opened) { + GWS_DNOT_REACHED(); + return NO; + } + _opened = YES; + return [_reader open:error]; +} + +- (void)performReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block { + GWS_DCHECK(_opened); + if ([_reader respondsToSelector:@selector(asyncReadDataWithCompletion:)]) { + [_reader asyncReadDataWithCompletion:[block copy]]; + } else { + NSError* error = nil; + NSData* data = [_reader readData:&error]; + block(data, error); + } +} + +- (void)performClose { + GWS_DCHECK(_opened); + [_reader close]; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithFormat:@"Status Code = %i", (int)_statusCode]; + if (_contentType) { + [description appendFormat:@"\nContent Type = %@", _contentType]; + } + if (_contentLength != NSUIntegerMax) { + [description appendFormat:@"\nContent Length = %lu", (unsigned long)_contentLength]; + } + [description appendFormat:@"\nCache Control Max Age = %lu", (unsigned long)_cacheControlMaxAge]; + if (_lastModifiedDate) { + [description appendFormat:@"\nLast Modified Date = %@", _lastModifiedDate]; + } + if (_eTag) { + [description appendFormat:@"\nETag = %@", _eTag]; + } + if (_additionalHeaders.count) { + [description appendString:@"\n"]; + for (NSString* header in [[_additionalHeaders allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [description appendFormat:@"\n%@: %@", header, [_additionalHeaders objectForKey:header]]; + } + } + return description; +} + +@end + +@implementation GCDWebServerResponse (Extensions) + ++ (instancetype)responseWithStatusCode:(NSInteger)statusCode { + return [(GCDWebServerResponse*)[self alloc] initWithStatusCode:statusCode]; +} + ++ (instancetype)responseWithRedirect:(NSURL*)location permanent:(BOOL)permanent { + return [(GCDWebServerResponse*)[self alloc] initWithRedirect:location permanent:permanent]; +} + +- (instancetype)initWithStatusCode:(NSInteger)statusCode { + if ((self = [self init])) { + self.statusCode = statusCode; + } + return self; +} + +- (instancetype)initWithRedirect:(NSURL*)location permanent:(BOOL)permanent { + if ((self = [self init])) { + self.statusCode = permanent ? kGCDWebServerHTTPStatusCode_MovedPermanently : kGCDWebServerHTTPStatusCode_TemporaryRedirect; + [self setValue:[location absoluteString] forAdditionalHeader:@"Location"]; + } + return self; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerDataRequest.h b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerDataRequest.h new file mode 100644 index 0000000000..b105ce7667 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerDataRequest.h @@ -0,0 +1,64 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerDataRequest subclass of GCDWebServerRequest stores the body + * of the HTTP request in memory. + */ +@interface GCDWebServerDataRequest : GCDWebServerRequest + +/** + * Returns the data for the request body. + */ +@property(nonatomic, readonly) NSData* data; + +@end + +@interface GCDWebServerDataRequest (Extensions) + +/** + * Returns the data for the request body interpreted as text. If the content + * type of the body is not a text one, or if an error occurs, nil is returned. + * + * The text encoding used to interpret the data is extracted from the + * "Content-Type" header or defaults to UTF-8. + */ +@property(nonatomic, readonly, nullable) NSString* text; + +/** + * Returns the data for the request body interpreted as a JSON object. If the + * content type of the body is not JSON, or if an error occurs, nil is returned. + */ +@property(nonatomic, readonly, nullable) id jsonObject; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerDataRequest.m b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerDataRequest.m new file mode 100644 index 0000000000..fcb293211f --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerDataRequest.m @@ -0,0 +1,104 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@interface GCDWebServerDataRequest () +@property(nonatomic) NSMutableData* data; +@end + +@implementation GCDWebServerDataRequest { + NSString* _text; + id _jsonObject; +} + +- (BOOL)open:(NSError**)error { + if (self.contentLength != NSUIntegerMax) { + _data = [[NSMutableData alloc] initWithCapacity:self.contentLength]; + } else { + _data = [[NSMutableData alloc] init]; + } + if (_data == nil) { + if (error) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed allocating memory"}]; + } + return NO; + } + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + [_data appendData:data]; + return YES; +} + +- (BOOL)close:(NSError**)error { + return YES; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + if (_data) { + [description appendString:@"\n\n"]; + [description appendString:GCDWebServerDescribeData(_data, (NSString*)self.contentType)]; + } + return description; +} + +@end + +@implementation GCDWebServerDataRequest (Extensions) + +- (NSString*)text { + if (_text == nil) { + if ([self.contentType hasPrefix:@"text/"]) { + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); + _text = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; + } else { + GWS_DNOT_REACHED(); + } + } + return _text; +} + +- (id)jsonObject { + if (_jsonObject == nil) { + NSString* mimeType = GCDWebServerTruncateHeaderValue(self.contentType); + if ([mimeType isEqualToString:@"application/json"] || [mimeType isEqualToString:@"text/json"] || [mimeType isEqualToString:@"text/javascript"]) { + _jsonObject = [NSJSONSerialization JSONObjectWithData:_data options:0 error:NULL]; + } else { + GWS_DNOT_REACHED(); + } + } + return _jsonObject; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerFileRequest.h b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerFileRequest.h new file mode 100644 index 0000000000..3c6a6d2492 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerFileRequest.h @@ -0,0 +1,49 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerFileRequest subclass of GCDWebServerRequest stores the body + * of the HTTP request to a file on disk. + */ +@interface GCDWebServerFileRequest : GCDWebServerRequest + +/** + * Returns the path to the temporary file containing the request body. + * + * @warning This temporary file will be automatically deleted when the + * GCDWebServerFileRequest is deallocated. If you want to preserve this file, + * you must move it to a different location beforehand. + */ +@property(nonatomic, readonly) NSString* temporaryPath; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerFileRequest.m b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerFileRequest.m new file mode 100644 index 0000000000..0845d5e961 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerFileRequest.m @@ -0,0 +1,102 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerFileRequest { + int _file; +} + +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { + if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { + _temporaryPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + } + return self; +} + +- (void)dealloc { + unlink([_temporaryPath fileSystemRepresentation]); +} + +- (BOOL)open:(NSError**)error { + _file = open([_temporaryPath fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (_file <= 0) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return NO; + } + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + if (write(_file, data.bytes, data.length) != (ssize_t)data.length) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return NO; + } + return YES; +} + +- (BOOL)close:(NSError**)error { + if (close(_file) < 0) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return NO; + } +#ifdef __GCDWEBSERVER_ENABLE_TESTING__ + NSString* creationDateHeader = [self.headers objectForKey:@"X-GCDWebServer-CreationDate"]; + if (creationDateHeader) { + NSDate* date = GCDWebServerParseISO8601(creationDateHeader); + if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileCreationDate : date} ofItemAtPath:_temporaryPath error:error]) { + return NO; + } + } + NSString* modifiedDateHeader = [self.headers objectForKey:@"X-GCDWebServer-ModifiedDate"]; + if (modifiedDateHeader) { + NSDate* date = GCDWebServerParseRFC822(modifiedDateHeader); + if (!date || ![[NSFileManager defaultManager] setAttributes:@{NSFileModificationDate : date} ofItemAtPath:_temporaryPath error:error]) { + return NO; + } + } +#endif + return YES; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendFormat:@"\n\n{%@}", _temporaryPath]; + return description; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h new file mode 100644 index 0000000000..cbe838eaab --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.h @@ -0,0 +1,136 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerMultiPart class is an abstract class that wraps the content + * of a part. + */ +@interface GCDWebServerMultiPart : NSObject + +/** + * Returns the control name retrieved from the part headers. + */ +@property(nonatomic, readonly) NSString* controlName; + +/** + * Returns the content type retrieved from the part headers or "text/plain" + * if not available (per HTTP specifications). + */ +@property(nonatomic, readonly) NSString* contentType; + +/** + * Returns the MIME type component of the content type for the part. + */ +@property(nonatomic, readonly) NSString* mimeType; + +@end + +/** + * The GCDWebServerMultiPartArgument subclass of GCDWebServerMultiPart wraps + * the content of a part as data in memory. + */ +@interface GCDWebServerMultiPartArgument : GCDWebServerMultiPart + +/** + * Returns the data for the part. + */ +@property(nonatomic, readonly) NSData* data; + +/** + * Returns the data for the part interpreted as text. If the content + * type of the part is not a text one, or if an error occurs, nil is returned. + * + * The text encoding used to interpret the data is extracted from the + * "Content-Type" header or defaults to UTF-8. + */ +@property(nonatomic, readonly, nullable) NSString* string; + +@end + +/** + * The GCDWebServerMultiPartFile subclass of GCDWebServerMultiPart wraps + * the content of a part as a file on disk. + */ +@interface GCDWebServerMultiPartFile : GCDWebServerMultiPart + +/** + * Returns the file name retrieved from the part headers. + */ +@property(nonatomic, readonly) NSString* fileName; + +/** + * Returns the path to the temporary file containing the part data. + * + * @warning This temporary file will be automatically deleted when the + * GCDWebServerMultiPartFile is deallocated. If you want to preserve this file, + * you must move it to a different location beforehand. + */ +@property(nonatomic, readonly) NSString* temporaryPath; + +@end + +/** + * The GCDWebServerMultiPartFormRequest subclass of GCDWebServerRequest + * parses the body of the HTTP request as a multipart encoded form. + */ +@interface GCDWebServerMultiPartFormRequest : GCDWebServerRequest + +/** + * Returns the argument parts from the multipart encoded form as + * name / GCDWebServerMultiPartArgument pairs. + */ +@property(nonatomic, readonly) NSArray* arguments; + +/** + * Returns the files parts from the multipart encoded form as + * name / GCDWebServerMultiPartFile pairs. + */ +@property(nonatomic, readonly) NSArray* files; + +/** + * Returns the MIME type for multipart encoded forms + * i.e. "multipart/form-data". + */ ++ (NSString*)mimeType; + +/** + * Returns the first argument for a given control name or nil if not found. + */ +- (nullable GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name; + +/** + * Returns the first file for a given control name or nil if not found. + */ +- (nullable GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m new file mode 100644 index 0000000000..7550a021fd --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerMultiPartFormRequest.m @@ -0,0 +1,405 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +#define kMultiPartBufferSize (256 * 1024) + +typedef enum { + kParserState_Undefined = 0, + kParserState_Start, + kParserState_Headers, + kParserState_Content, + kParserState_End +} ParserState; + +@interface GCDWebServerMIMEStreamParser : NSObject +@end + +static NSData* _newlineData = nil; +static NSData* _newlinesData = nil; +static NSData* _dashNewlineData = nil; + +@implementation GCDWebServerMultiPart + +- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type { + if ((self = [super init])) { + _controlName = [name copy]; + _contentType = [type copy]; + _mimeType = (NSString*)GCDWebServerTruncateHeaderValue(_contentType); + } + return self; +} + +@end + +@implementation GCDWebServerMultiPartArgument + +- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type data:(NSData* _Nonnull)data { + if ((self = [super initWithControlName:name contentType:type])) { + _data = data; + + if ([self.contentType hasPrefix:@"text/"]) { + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); + _string = [[NSString alloc] initWithData:_data encoding:GCDWebServerStringEncodingFromCharset(charset)]; + } + } + return self; +} + +- (NSString*)description { + return [NSString stringWithFormat:@"<%@ | '%@' | %lu bytes>", [self class], self.mimeType, (unsigned long)_data.length]; +} + +@end + +@implementation GCDWebServerMultiPartFile + +- (instancetype)initWithControlName:(NSString* _Nonnull)name contentType:(NSString* _Nonnull)type fileName:(NSString* _Nonnull)fileName temporaryPath:(NSString* _Nonnull)temporaryPath { + if ((self = [super initWithControlName:name contentType:type])) { + _fileName = [fileName copy]; + _temporaryPath = [temporaryPath copy]; + } + return self; +} + +- (void)dealloc { + unlink([_temporaryPath fileSystemRepresentation]); +} + +- (NSString*)description { + return [NSString stringWithFormat:@"<%@ | '%@' | '%@>'", [self class], self.mimeType, _fileName]; +} + +@end + +@implementation GCDWebServerMIMEStreamParser { + NSData* _boundary; + NSString* _defaultcontrolName; + ParserState _state; + NSMutableData* _data; + NSMutableArray* _arguments; + NSMutableArray* _files; + + NSString* _controlName; + NSString* _fileName; + NSString* _contentType; + NSString* _tmpPath; + int _tmpFile; + GCDWebServerMIMEStreamParser* _subParser; +} + ++ (void)initialize { + if (_newlineData == nil) { + _newlineData = [[NSData alloc] initWithBytes:"\r\n" length:2]; + GWS_DCHECK(_newlineData); + } + if (_newlinesData == nil) { + _newlinesData = [[NSData alloc] initWithBytes:"\r\n\r\n" length:4]; + GWS_DCHECK(_newlinesData); + } + if (_dashNewlineData == nil) { + _dashNewlineData = [[NSData alloc] initWithBytes:"--\r\n" length:4]; + GWS_DCHECK(_dashNewlineData); + } +} + +- (instancetype)initWithBoundary:(NSString* _Nonnull)boundary defaultControlName:(NSString* _Nullable)name arguments:(NSMutableArray* _Nonnull)arguments files:(NSMutableArray* _Nonnull)files { + NSData* data = boundary.length ? [[NSString stringWithFormat:@"--%@", boundary] dataUsingEncoding:NSASCIIStringEncoding] : nil; + if (data == nil) { + GWS_DNOT_REACHED(); + return nil; + } + if ((self = [super init])) { + _boundary = data; + _defaultcontrolName = name; + _arguments = arguments; + _files = files; + _data = [[NSMutableData alloc] initWithCapacity:kMultiPartBufferSize]; + _state = kParserState_Start; + } + return self; +} + +- (void)dealloc { + if (_tmpFile > 0) { + close(_tmpFile); + unlink([_tmpPath fileSystemRepresentation]); + } +} + +// http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 +- (BOOL)_parseData { + BOOL success = YES; + + if (_state == kParserState_Headers) { + NSRange range = [_data rangeOfData:_newlinesData options:0 range:NSMakeRange(0, _data.length)]; + if (range.location != NSNotFound) { + _controlName = nil; + _fileName = nil; + _contentType = nil; + _tmpPath = nil; + _subParser = nil; + NSString* headers = [[NSString alloc] initWithData:[_data subdataWithRange:NSMakeRange(0, range.location)] encoding:NSUTF8StringEncoding]; + if (headers) { + for (NSString* header in [headers componentsSeparatedByString:@"\r\n"]) { + NSRange subRange = [header rangeOfString:@":"]; + if (subRange.location != NSNotFound) { + NSString* name = [header substringToIndex:subRange.location]; + NSString* value = [[header substringFromIndex:(subRange.location + subRange.length)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + if ([name caseInsensitiveCompare:@"Content-Type"] == NSOrderedSame) { + _contentType = GCDWebServerNormalizeHeaderValue(value); + } else if ([name caseInsensitiveCompare:@"Content-Disposition"] == NSOrderedSame) { + NSString* contentDisposition = GCDWebServerNormalizeHeaderValue(value); + if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"form-data"]) { + _controlName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"name"); + _fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); + } else if ([GCDWebServerTruncateHeaderValue(contentDisposition) isEqualToString:@"file"]) { + _controlName = _defaultcontrolName; + _fileName = GCDWebServerExtractHeaderValueParameter(contentDisposition, @"filename"); + } + } + } else { + GWS_DNOT_REACHED(); + } + } + if (_contentType == nil) { + _contentType = @"text/plain"; + } + } else { + GWS_LOG_ERROR(@"Failed decoding headers in part of 'multipart/form-data'"); + GWS_DNOT_REACHED(); + } + if (_controlName) { + if ([GCDWebServerTruncateHeaderValue(_contentType) isEqualToString:@"multipart/mixed"]) { + NSString* boundary = GCDWebServerExtractHeaderValueParameter(_contentType, @"boundary"); + _subParser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:_controlName arguments:_arguments files:_files]; + if (_subParser == nil) { + GWS_DNOT_REACHED(); + success = NO; + } + } else if (_fileName) { + NSString* path = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; + _tmpFile = open([path fileSystemRepresentation], O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (_tmpFile > 0) { + _tmpPath = [path copy]; + } else { + GWS_DNOT_REACHED(); + success = NO; + } + } + } else { + GWS_DNOT_REACHED(); + success = NO; + } + + [_data replaceBytesInRange:NSMakeRange(0, range.location + range.length) withBytes:NULL length:0]; + _state = kParserState_Content; + } + } + + if ((_state == kParserState_Start) || (_state == kParserState_Content)) { + NSRange range = [_data rangeOfData:_boundary options:0 range:NSMakeRange(0, _data.length)]; + if (range.location != NSNotFound) { + NSRange subRange = NSMakeRange(range.location + range.length, _data.length - range.location - range.length); + NSRange subRange1 = [_data rangeOfData:_newlineData options:NSDataSearchAnchored range:subRange]; + NSRange subRange2 = [_data rangeOfData:_dashNewlineData options:NSDataSearchAnchored range:subRange]; + if ((subRange1.location != NSNotFound) || (subRange2.location != NSNotFound)) { + if (_state == kParserState_Content) { + const void* dataBytes = _data.bytes; + NSUInteger dataLength = range.location - 2; + if (_subParser) { + if (![_subParser appendBytes:dataBytes length:(dataLength + 2)] || ![_subParser isAtEnd]) { + GWS_DNOT_REACHED(); + success = NO; + } + _subParser = nil; + } else if (_tmpPath) { + ssize_t result = write(_tmpFile, dataBytes, dataLength); + if (result == (ssize_t)dataLength) { + if (close(_tmpFile) == 0) { + _tmpFile = 0; + GCDWebServerMultiPartFile* file = [[GCDWebServerMultiPartFile alloc] initWithControlName:_controlName contentType:_contentType fileName:_fileName temporaryPath:_tmpPath]; + [_files addObject:file]; + } else { + GWS_DNOT_REACHED(); + success = NO; + } + } else { + GWS_DNOT_REACHED(); + success = NO; + } + _tmpPath = nil; + } else { + NSData* data = [[NSData alloc] initWithBytes:(void*)dataBytes length:dataLength]; + GCDWebServerMultiPartArgument* argument = [[GCDWebServerMultiPartArgument alloc] initWithControlName:_controlName contentType:_contentType data:data]; + [_arguments addObject:argument]; + } + } + + if (subRange1.location != NSNotFound) { + [_data replaceBytesInRange:NSMakeRange(0, subRange1.location + subRange1.length) withBytes:NULL length:0]; + _state = kParserState_Headers; + success = [self _parseData]; + } else { + _state = kParserState_End; + } + } + } else { + NSUInteger margin = 2 * _boundary.length; + if (_data.length > margin) { + NSUInteger length = _data.length - margin; + if (_subParser) { + if ([_subParser appendBytes:_data.bytes length:length]) { + [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; + } else { + GWS_DNOT_REACHED(); + success = NO; + } + } else if (_tmpPath) { + ssize_t result = write(_tmpFile, _data.bytes, length); + if (result == (ssize_t)length) { + [_data replaceBytesInRange:NSMakeRange(0, length) withBytes:NULL length:0]; + } else { + GWS_DNOT_REACHED(); + success = NO; + } + } + } + } + } + + return success; +} + +- (BOOL)appendBytes:(const void*)bytes length:(NSUInteger)length { + [_data appendBytes:bytes length:length]; + return [self _parseData]; +} + +- (BOOL)isAtEnd { + return (_state == kParserState_End); +} + +@end + +@interface GCDWebServerMultiPartFormRequest () +@property(nonatomic) NSMutableArray* arguments; +@property(nonatomic) NSMutableArray* files; +@end + +@implementation GCDWebServerMultiPartFormRequest { + GCDWebServerMIMEStreamParser* _parser; +} + ++ (NSString*)mimeType { + return @"multipart/form-data"; +} + +- (instancetype)initWithMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers path:(NSString*)path query:(NSDictionary*)query { + if ((self = [super initWithMethod:method url:url headers:headers path:path query:query])) { + _arguments = [[NSMutableArray alloc] init]; + _files = [[NSMutableArray alloc] init]; + } + return self; +} + +- (BOOL)open:(NSError**)error { + NSString* boundary = GCDWebServerExtractHeaderValueParameter(self.contentType, @"boundary"); + _parser = [[GCDWebServerMIMEStreamParser alloc] initWithBoundary:boundary defaultControlName:nil arguments:_arguments files:_files]; + if (_parser == nil) { + if (error) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed starting to parse multipart form data"}]; + } + return NO; + } + return YES; +} + +- (BOOL)writeData:(NSData*)data error:(NSError**)error { + if (![_parser appendBytes:data.bytes length:data.length]) { + if (error) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed continuing to parse multipart form data"}]; + } + return NO; + } + return YES; +} + +- (BOOL)close:(NSError**)error { + BOOL atEnd = [_parser isAtEnd]; + _parser = nil; + if (!atEnd) { + if (error) { + *error = [NSError errorWithDomain:kGCDWebServerErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Failed finishing to parse multipart form data"}]; + } + return NO; + } + return YES; +} + +- (GCDWebServerMultiPartArgument*)firstArgumentForControlName:(NSString*)name { + for (GCDWebServerMultiPartArgument* argument in _arguments) { + if ([argument.controlName isEqualToString:name]) { + return argument; + } + } + return nil; +} + +- (GCDWebServerMultiPartFile*)firstFileForControlName:(NSString*)name { + for (GCDWebServerMultiPartFile* file in _files) { + if ([file.controlName isEqualToString:name]) { + return file; + } + } + return nil; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + if (_arguments.count) { + [description appendString:@"\n"]; + for (GCDWebServerMultiPartArgument* argument in _arguments) { + [description appendFormat:@"\n%@ (%@)\n", argument.controlName, argument.contentType]; + [description appendString:GCDWebServerDescribeData(argument.data, argument.contentType)]; + } + } + if (_files.count) { + [description appendString:@"\n"]; + for (GCDWebServerMultiPartFile* file in _files) { + [description appendFormat:@"\n%@ (%@): %@\n{%@}", file.controlName, file.contentType, file.fileName, file.temporaryPath]; + } + } + return description; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h new file mode 100644 index 0000000000..c0168b7617 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.h @@ -0,0 +1,55 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerDataRequest.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerURLEncodedFormRequest subclass of GCDWebServerRequest + * parses the body of the HTTP request as a URL encoded form using + * GCDWebServerParseURLEncodedForm(). + */ +@interface GCDWebServerURLEncodedFormRequest : GCDWebServerDataRequest + +/** + * Returns the unescaped control names and values for the URL encoded form. + * + * The text encoding used to interpret the data is extracted from the + * "Content-Type" header or defaults to UTF-8. + */ +@property(nonatomic, readonly) NSDictionary* arguments; + +/** + * Returns the MIME type for URL encoded forms + * i.e. "application/x-www-form-urlencoded". + */ ++ (NSString*)mimeType; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m new file mode 100644 index 0000000000..d4d89e43e8 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Requests/GCDWebServerURLEncodedFormRequest.m @@ -0,0 +1,60 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerURLEncodedFormRequest + ++ (NSString*)mimeType { + return @"application/x-www-form-urlencoded"; +} + +- (BOOL)close:(NSError**)error { + if (![super close:error]) { + return NO; + } + + NSString* charset = GCDWebServerExtractHeaderValueParameter(self.contentType, @"charset"); + NSString* string = [[NSString alloc] initWithData:self.data encoding:GCDWebServerStringEncodingFromCharset(charset)]; + _arguments = GCDWebServerParseURLEncodedForm(string); + return YES; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendString:@"\n"]; + for (NSString* argument in [[_arguments allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [description appendFormat:@"\n%@ = %@", argument, [_arguments objectForKey:argument]]; + } + return description; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h new file mode 100644 index 0000000000..e5121c3362 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerDataResponse.h @@ -0,0 +1,113 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerDataResponse subclass of GCDWebServerResponse reads the body + * of the HTTP response from memory. + */ +@interface GCDWebServerDataResponse : GCDWebServerResponse +@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null + +/** + * Creates a response with data in memory and a given content type. + */ ++ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type; + +@end + +@interface GCDWebServerDataResponse (Extensions) + +/** + * Creates a data response from text encoded using UTF-8. + */ ++ (nullable instancetype)responseWithText:(NSString*)text; + +/** + * Creates a data response from HTML encoded using UTF-8. + */ ++ (nullable instancetype)responseWithHTML:(NSString*)html; + +/** + * Creates a data response from an HTML template encoded using UTF-8. + * See -initWithHTMLTemplate:variables: for details. + */ ++ (nullable instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; + +/** + * Creates a data response from a serialized JSON object and the default + * "application/json" content type. + */ ++ (nullable instancetype)responseWithJSONObject:(id)object; + +/** + * Creates a data response from a serialized JSON object and a custom + * content type. + */ ++ (nullable instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type; + +/** + * Initializes a data response from text encoded using UTF-8. + */ +- (nullable instancetype)initWithText:(NSString*)text; + +/** + * Initializes a data response from HTML encoded using UTF-8. + */ +- (nullable instancetype)initWithHTML:(NSString*)html; + +/** + * Initializes a data response from an HTML template encoded using UTF-8. + * + * All occurences of "%variable%" within the HTML template are replaced with + * their corresponding values. + */ +- (nullable instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables; + +/** + * Initializes a data response from a serialized JSON object and the default + * "application/json" content type. + */ +- (nullable instancetype)initWithJSONObject:(id)object; + +/** + * Initializes a data response from a serialized JSON object and a custom + * content type. + */ +- (nullable instancetype)initWithJSONObject:(id)object contentType:(NSString*)type; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerDataResponse.m b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerDataResponse.m new file mode 100644 index 0000000000..2ea2ce904d --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerDataResponse.m @@ -0,0 +1,136 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerDataResponse { + NSData* _data; + BOOL _done; +} + +@dynamic contentType; + ++ (instancetype)responseWithData:(NSData*)data contentType:(NSString*)type { + return [(GCDWebServerDataResponse*)[[self class] alloc] initWithData:data contentType:type]; +} + +- (instancetype)initWithData:(NSData*)data contentType:(NSString*)type { + if ((self = [super init])) { + _data = data; + + self.contentType = type; + self.contentLength = data.length; + } + return self; +} + +- (NSData*)readData:(NSError**)error { + NSData* data; + if (_done) { + data = [NSData data]; + } else { + data = _data; + _done = YES; + } + return data; +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendString:@"\n\n"]; + [description appendString:GCDWebServerDescribeData(_data, self.contentType)]; + return description; +} + +@end + +@implementation GCDWebServerDataResponse (Extensions) + ++ (instancetype)responseWithText:(NSString*)text { + return [(GCDWebServerDataResponse*)[self alloc] initWithText:text]; +} + ++ (instancetype)responseWithHTML:(NSString*)html { + return [(GCDWebServerDataResponse*)[self alloc] initWithHTML:html]; +} + ++ (instancetype)responseWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { + return [(GCDWebServerDataResponse*)[self alloc] initWithHTMLTemplate:path variables:variables]; +} + ++ (instancetype)responseWithJSONObject:(id)object { + return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object]; +} + ++ (instancetype)responseWithJSONObject:(id)object contentType:(NSString*)type { + return [(GCDWebServerDataResponse*)[self alloc] initWithJSONObject:object contentType:type]; +} + +- (instancetype)initWithText:(NSString*)text { + NSData* data = [text dataUsingEncoding:NSUTF8StringEncoding]; + if (data == nil) { + GWS_DNOT_REACHED(); + return nil; + } + return [self initWithData:data contentType:@"text/plain; charset=utf-8"]; +} + +- (instancetype)initWithHTML:(NSString*)html { + NSData* data = [html dataUsingEncoding:NSUTF8StringEncoding]; + if (data == nil) { + GWS_DNOT_REACHED(); + return nil; + } + return [self initWithData:data contentType:@"text/html; charset=utf-8"]; +} + +- (instancetype)initWithHTMLTemplate:(NSString*)path variables:(NSDictionary*)variables { + NSMutableString* html = [[NSMutableString alloc] initWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL]; + [variables enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSString* value, BOOL* stop) { + [html replaceOccurrencesOfString:[NSString stringWithFormat:@"%%%@%%", key] withString:value options:0 range:NSMakeRange(0, html.length)]; + }]; + return [self initWithHTML:html]; +} + +- (instancetype)initWithJSONObject:(id)object { + return [self initWithJSONObject:object contentType:@"application/json"]; +} + +- (instancetype)initWithJSONObject:(id)object contentType:(NSString*)type { + NSData* data = [NSJSONSerialization dataWithJSONObject:object options:0 error:NULL]; + if (data == nil) { + GWS_DNOT_REACHED(); + return nil; + } + return [self initWithData:data contentType:type]; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h new file mode 100644 index 0000000000..7e10d5bfe9 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.h @@ -0,0 +1,85 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerDataResponse.h" +#import "GCDWebServerHTTPStatusCodes.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerDataResponse subclass of GCDWebServerDataResponse generates + * an HTML body from an HTTP status code and an error message. + */ +@interface GCDWebServerErrorResponse : GCDWebServerDataResponse + +/** + * Creates a client error response with the corresponding HTTP status code. + */ ++ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); + +/** + * Creates a server error response with the corresponding HTTP status code. + */ ++ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); + +/** + * Creates a client error response with the corresponding HTTP status code + * and an underlying NSError. + */ ++ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); + +/** + * Creates a server error response with the corresponding HTTP status code + * and an underlying NSError. + */ ++ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); + +/** + * Initializes a client error response with the corresponding HTTP status code. + */ +- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); + +/** + * Initializes a server error response with the corresponding HTTP status code. + */ +- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... NS_FORMAT_FUNCTION(2, 3); + +/** + * Initializes a client error response with the corresponding HTTP status code + * and an underlying NSError. + */ +- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); + +/** + * Initializes a server error response with the corresponding HTTP status code + * and an underlying NSError. + */ +- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(nullable NSError*)underlyingError message:(NSString*)format, ... NS_FORMAT_FUNCTION(3, 4); + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.m b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.m new file mode 100644 index 0000000000..ab3788f6b8 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerErrorResponse.m @@ -0,0 +1,124 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerErrorResponse + ++ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); + va_list arguments; + va_start(arguments, format); + GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; + va_end(arguments); + return response; +} + ++ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); + va_list arguments; + va_start(arguments, format); + GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; + va_end(arguments); + return response; +} + ++ (instancetype)responseWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); + va_list arguments; + va_start(arguments, format); + GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; + va_end(arguments); + return response; +} + ++ (instancetype)responseWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); + va_list arguments; + va_start(arguments, format); + GCDWebServerErrorResponse* response = [(GCDWebServerErrorResponse*)[self alloc] initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; + va_end(arguments); + return response; +} + +static inline NSString* _EscapeHTMLString(NSString* string) { + return [string stringByReplacingOccurrencesOfString:@"\"" withString:@"""]; +} + +- (instancetype)initWithStatusCode:(NSInteger)statusCode underlyingError:(NSError*)underlyingError messageFormat:(NSString*)format arguments:(va_list)arguments { + NSString* message = [[NSString alloc] initWithFormat:format arguments:arguments]; + NSString* title = [NSString stringWithFormat:@"HTTP Error %i", (int)statusCode]; + NSString* error = underlyingError ? [NSString stringWithFormat:@"[%@] %@ (%li)", underlyingError.domain, _EscapeHTMLString(underlyingError.localizedDescription), (long)underlyingError.code] : @""; + NSString* html = [NSString stringWithFormat:@"%@

%@: %@

%@

", + title, title, _EscapeHTMLString(message), error]; + if ((self = [self initWithHTML:html])) { + self.statusCode = statusCode; + } + return self; +} + +- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); + va_list arguments; + va_start(arguments, format); + self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; + va_end(arguments); + return self; +} + +- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); + va_list arguments; + va_start(arguments, format); + self = [self initWithStatusCode:errorCode underlyingError:nil messageFormat:format arguments:arguments]; + va_end(arguments); + return self; +} + +- (instancetype)initWithClientError:(GCDWebServerClientErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 400) && ((NSInteger)errorCode < 500)); + va_list arguments; + va_start(arguments, format); + self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; + va_end(arguments); + return self; +} + +- (instancetype)initWithServerError:(GCDWebServerServerErrorHTTPStatusCode)errorCode underlyingError:(NSError*)underlyingError message:(NSString*)format, ... { + GWS_DCHECK(((NSInteger)errorCode >= 500) && ((NSInteger)errorCode < 600)); + va_list arguments; + va_start(arguments, format); + self = [self initWithStatusCode:errorCode underlyingError:underlyingError messageFormat:format arguments:arguments]; + va_end(arguments); + return self; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h new file mode 100644 index 0000000000..78931bc615 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerFileResponse.h @@ -0,0 +1,108 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerFileResponse subclass of GCDWebServerResponse reads the body + * of the HTTP response from a file on disk. + * + * It will automatically set the contentType, lastModifiedDate and eTag + * properties of the GCDWebServerResponse according to the file extension and + * metadata. + */ +@interface GCDWebServerFileResponse : GCDWebServerResponse +@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null +@property(nonatomic) NSDate* lastModifiedDate; // Redeclare as non-null +@property(nonatomic, copy) NSString* eTag; // Redeclare as non-null + +/** + * Creates a response with the contents of a file. + */ ++ (nullable instancetype)responseWithFile:(NSString*)path; + +/** + * Creates a response like +responseWithFile: and sets the "Content-Disposition" + * HTTP header for a download if the "attachment" argument is YES. + */ ++ (nullable instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment; + +/** + * Creates a response like +responseWithFile: but restricts the file contents + * to a specific byte range. + * + * See -initWithFile:byteRange: for details. + */ ++ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range; + +/** + * Creates a response like +responseWithFile:byteRange: and sets the + * "Content-Disposition" HTTP header for a download if the "attachment" + * argument is YES. + */ ++ (nullable instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment; + +/** + * Initializes a response with the contents of a file. + */ +- (nullable instancetype)initWithFile:(NSString*)path; + +/** + * Initializes a response like +responseWithFile: and sets the + * "Content-Disposition" HTTP header for a download if the "attachment" + * argument is YES. + */ +- (nullable instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment; + +/** + * Initializes a response like -initWithFile: but restricts the file contents + * to a specific byte range. This range should be set to (NSUIntegerMax, 0) for + * the full file, (offset, length) if expressed from the beginning of the file, + * or (NSUIntegerMax, length) if expressed from the end of the file. The "offset" + * and "length" values will be automatically adjusted to be compatible with the + * actual size of the file. + * + * This argument would typically be set to the value of the byteRange property + * of the current GCDWebServerRequest. + */ +- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range; + +/** + * This method is the designated initializer for the class. + * + * If MIME type overrides are specified, they allow to customize the built-in + * mapping from extensions to MIME types. Keys of the dictionary must be lowercased + * file extensions without the period, and the values must be the corresponding + * MIME types. + */ +- (nullable instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(nullable NSDictionary*)overrides; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerFileResponse.m b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerFileResponse.m new file mode 100644 index 0000000000..78233063e0 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerFileResponse.m @@ -0,0 +1,185 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import + +#import "GCDWebServerPrivate.h" + +#define kFileReadBufferSize (32 * 1024) + +@implementation GCDWebServerFileResponse { + NSString* _path; + NSUInteger _offset; + NSUInteger _size; + int _file; +} + +@dynamic contentType, lastModifiedDate, eTag; + ++ (instancetype)responseWithFile:(NSString*)path { + return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path]; +} + ++ (instancetype)responseWithFile:(NSString*)path isAttachment:(BOOL)attachment { + return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path isAttachment:attachment]; +} + ++ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range { + return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range]; +} + ++ (instancetype)responseWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment { + return [(GCDWebServerFileResponse*)[[self class] alloc] initWithFile:path byteRange:range isAttachment:attachment mimeTypeOverrides:nil]; +} + +- (instancetype)initWithFile:(NSString*)path { + return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:NO mimeTypeOverrides:nil]; +} + +- (instancetype)initWithFile:(NSString*)path isAttachment:(BOOL)attachment { + return [self initWithFile:path byteRange:NSMakeRange(NSUIntegerMax, 0) isAttachment:attachment mimeTypeOverrides:nil]; +} + +- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range { + return [self initWithFile:path byteRange:range isAttachment:NO mimeTypeOverrides:nil]; +} + +static inline NSDate* _NSDateFromTimeSpec(const struct timespec* t) { + return [NSDate dateWithTimeIntervalSince1970:((NSTimeInterval)t->tv_sec + (NSTimeInterval)t->tv_nsec / 1000000000.0)]; +} + +- (instancetype)initWithFile:(NSString*)path byteRange:(NSRange)range isAttachment:(BOOL)attachment mimeTypeOverrides:(NSDictionary*)overrides { + struct stat info; + if (lstat([path fileSystemRepresentation], &info) || !(info.st_mode & S_IFREG)) { + GWS_DNOT_REACHED(); + return nil; + } +#ifndef __LP64__ + if (info.st_size >= (off_t)4294967295) { // In 32 bit mode, we can't handle files greater than 4 GiBs (don't use "NSUIntegerMax" here to avoid potential unsigned to signed conversion issues) + GWS_DNOT_REACHED(); + return nil; + } +#endif + NSUInteger fileSize = (NSUInteger)info.st_size; + + BOOL hasByteRange = GCDWebServerIsValidByteRange(range); + if (hasByteRange) { + if (range.location != NSUIntegerMax) { + range.location = MIN(range.location, fileSize); + range.length = MIN(range.length, fileSize - range.location); + } else { + range.length = MIN(range.length, fileSize); + range.location = fileSize - range.length; + } + if (range.length == 0) { + return nil; // TODO: Return 416 status code and "Content-Range: bytes */{file length}" header + } + } else { + range.location = 0; + range.length = fileSize; + } + + if ((self = [super init])) { + _path = [path copy]; + _offset = range.location; + _size = range.length; + if (hasByteRange) { + [self setStatusCode:kGCDWebServerHTTPStatusCode_PartialContent]; + [self setValue:[NSString stringWithFormat:@"bytes %lu-%lu/%lu", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), (unsigned long)fileSize] forAdditionalHeader:@"Content-Range"]; + GWS_LOG_DEBUG(@"Using content bytes range [%lu-%lu] for file \"%@\"", (unsigned long)_offset, (unsigned long)(_offset + _size - 1), path); + } + + if (attachment) { + NSString* fileName = [path lastPathComponent]; + NSData* data = [[fileName stringByReplacingOccurrencesOfString:@"\"" withString:@""] dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:YES]; + NSString* lossyFileName = data ? [[NSString alloc] initWithData:data encoding:NSISOLatin1StringEncoding] : nil; + if (lossyFileName) { + NSString* value = [NSString stringWithFormat:@"attachment; filename=\"%@\"; filename*=UTF-8''%@", lossyFileName, GCDWebServerEscapeURLString(fileName)]; + [self setValue:value forAdditionalHeader:@"Content-Disposition"]; + } else { + GWS_DNOT_REACHED(); + } + } + + self.contentType = GCDWebServerGetMimeTypeForExtension([_path pathExtension], overrides); + self.contentLength = _size; + self.lastModifiedDate = _NSDateFromTimeSpec(&info.st_mtimespec); + self.eTag = [NSString stringWithFormat:@"%llu/%li/%li", info.st_ino, info.st_mtimespec.tv_sec, info.st_mtimespec.tv_nsec]; + } + return self; +} + +- (BOOL)open:(NSError**)error { + _file = open([_path fileSystemRepresentation], O_NOFOLLOW | O_RDONLY); + if (_file <= 0) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return NO; + } + if (lseek(_file, _offset, SEEK_SET) != (off_t)_offset) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + close(_file); + return NO; + } + return YES; +} + +- (NSData*)readData:(NSError**)error { + size_t length = MIN((NSUInteger)kFileReadBufferSize, _size); + NSMutableData* data = [[NSMutableData alloc] initWithLength:length]; + ssize_t result = read(_file, data.mutableBytes, length); + if (result < 0) { + if (error) { + *error = GCDWebServerMakePosixError(errno); + } + return nil; + } + if (result > 0) { + [data setLength:result]; + _size -= result; + } + return data; +} + +- (void)close { + close(_file); +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendFormat:@"\n\n{%@}", _path]; + return description; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h new file mode 100644 index 0000000000..211f630388 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.h @@ -0,0 +1,80 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#import "GCDWebServerResponse.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * The GCDWebServerStreamBlock is called to stream the data for the HTTP body. + * The block must return either a chunk of data, an empty NSData when done, or + * nil on error and set the "error" argument which is guaranteed to be non-NULL. + */ +typedef NSData* _Nullable (^GCDWebServerStreamBlock)(NSError** error); + +/** + * The GCDWebServerAsyncStreamBlock works like the GCDWebServerStreamBlock + * except the streamed data can be returned at a later time allowing for + * truly asynchronous generation of the data. + * + * The block must call "completionBlock" passing the new chunk of data when ready, + * an empty NSData when done, or nil on error and pass a NSError. + * + * The block cannot call "completionBlock" more than once per invocation. + */ +typedef void (^GCDWebServerAsyncStreamBlock)(GCDWebServerBodyReaderCompletionBlock completionBlock); + +/** + * The GCDWebServerStreamedResponse subclass of GCDWebServerResponse streams + * the body of the HTTP response using a GCD block. + */ +@interface GCDWebServerStreamedResponse : GCDWebServerResponse +@property(nonatomic, copy) NSString* contentType; // Redeclare as non-null + +/** + * Creates a response with streamed data and a given content type. + */ ++ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; + +/** + * Creates a response with async streamed data and a given content type. + */ ++ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; + +/** + * Initializes a response with streamed data and a given content type. + */ +- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block; + +/** + * This method is the designated initializer for the class. + */ +- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.m b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.m new file mode 100644 index 0000000000..c05e91b44f --- /dev/null +++ b/pkg/apple/WebServer/GCDWebServer/Responses/GCDWebServerStreamedResponse.m @@ -0,0 +1,76 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +#if !__has_feature(objc_arc) +#error GCDWebServer requires ARC +#endif + +#import "GCDWebServerPrivate.h" + +@implementation GCDWebServerStreamedResponse { + GCDWebServerAsyncStreamBlock _block; +} + +@dynamic contentType; + ++ (instancetype)responseWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { + return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type streamBlock:block]; +} + ++ (instancetype)responseWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block { + return [(GCDWebServerStreamedResponse*)[[self class] alloc] initWithContentType:type asyncStreamBlock:block]; +} + +- (instancetype)initWithContentType:(NSString*)type streamBlock:(GCDWebServerStreamBlock)block { + return [self initWithContentType:type + asyncStreamBlock:^(GCDWebServerBodyReaderCompletionBlock completionBlock) { + NSError* error = nil; + NSData* data = block(&error); + completionBlock(data, error); + }]; +} + +- (instancetype)initWithContentType:(NSString*)type asyncStreamBlock:(GCDWebServerAsyncStreamBlock)block { + if ((self = [super init])) { + _block = [block copy]; + + self.contentType = type; + } + return self; +} + +- (void)asyncReadDataWithCompletion:(GCDWebServerBodyReaderCompletionBlock)block { + _block(block); +} + +- (NSString*)description { + NSMutableString* description = [NSMutableString stringWithString:[super description]]; + [description appendString:@"\n\n"]; + return description; +} + +@end diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Info.plist b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Info.plist new file mode 100644 index 0000000000..e25dc75046 Binary files /dev/null and b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Info.plist differ diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/bootstrap-theme.css b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/bootstrap-theme.css new file mode 100644 index 0000000000..a4069929bc --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/bootstrap-theme.css @@ -0,0 +1,347 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); +} +.btn-default:active, +.btn-primary:active, +.btn-success:active, +.btn-info:active, +.btn-warning:active, +.btn-danger:active, +.btn-default.active, +.btn-primary.active, +.btn-success.active, +.btn-info.active, +.btn-warning.active, +.btn-danger.active { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn:active, +.btn.active { + background-image: none; +} +.btn-default { + text-shadow: 0 1px 0 #fff; + background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); + background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #dbdbdb; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus { + background-color: #e0e0e0; + background-position: 0 -15px; +} +.btn-default:active, +.btn-default.active { + background-color: #e0e0e0; + border-color: #dbdbdb; +} +.btn-primary { + background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #2b669a; +} +.btn-primary:hover, +.btn-primary:focus { + background-color: #2d6ca2; + background-position: 0 -15px; +} +.btn-primary:active, +.btn-primary.active { + background-color: #2d6ca2; + border-color: #2b669a; +} +.btn-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #3e8f3e; +} +.btn-success:hover, +.btn-success:focus { + background-color: #419641; + background-position: 0 -15px; +} +.btn-success:active, +.btn-success.active { + background-color: #419641; + border-color: #3e8f3e; +} +.btn-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #28a4c9; +} +.btn-info:hover, +.btn-info:focus { + background-color: #2aabd2; + background-position: 0 -15px; +} +.btn-info:active, +.btn-info.active { + background-color: #2aabd2; + border-color: #28a4c9; +} +.btn-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #e38d13; +} +.btn-warning:hover, +.btn-warning:focus { + background-color: #eb9316; + background-position: 0 -15px; +} +.btn-warning:active, +.btn-warning.active { + background-color: #eb9316; + border-color: #e38d13; +} +.btn-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #b92c28; +} +.btn-danger:hover, +.btn-danger:focus { + background-color: #c12e2a; + background-position: 0 -15px; +} +.btn-danger:active, +.btn-danger.active { + background-color: #c12e2a; + border-color: #b92c28; +} +.thumbnail, +.img-thumbnail { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background-color: #e8e8e8; + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + background-color: #357ebd; + background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); + background-repeat: repeat-x; +} +.navbar-default { + background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); + background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); +} +.navbar-default .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f3f3f3 100%); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f3f3f3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff3f3f3', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, .25); +} +.navbar-inverse { + background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; +} +.navbar-inverse .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #222 0%, #282828 100%); + background-image: linear-gradient(to bottom, #222 0%, #282828 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff282828', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); +} +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); +} +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} +.alert { + text-shadow: 0 1px 0 rgba(255, 255, 255, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); +} +.alert-success { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); + background-repeat: repeat-x; + border-color: #b2dba1; +} +.alert-info { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); + background-repeat: repeat-x; + border-color: #9acfea; +} +.alert-warning { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); + background-repeat: repeat-x; + border-color: #f5e79e; +} +.alert-danger { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); + background-repeat: repeat-x; + border-color: #dca7a7; +} +.progress { + background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar { + background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); + background-repeat: repeat-x; +} +.list-group { + border-radius: 4px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 #3071a9; + background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0); + background-repeat: repeat-x; + border-color: #3278b3; +} +.panel { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); + box-shadow: 0 1px 2px rgba(0, 0, 0, .05); +} +.panel-default > .panel-heading { + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.panel-primary > .panel-heading { + background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%); + background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0); + background-repeat: repeat-x; +} +.panel-success > .panel-heading { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); + background-repeat: repeat-x; +} +.panel-info > .panel-heading { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); + background-repeat: repeat-x; +} +.panel-warning > .panel-heading { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); + background-repeat: repeat-x; +} +.panel-danger > .panel-heading { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); + background-repeat: repeat-x; +} +.well { + background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; + border-color: #dcdcdc; + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); +} +/*# sourceMappingURL=bootstrap-theme.css.map */ diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/bootstrap.css b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/bootstrap.css new file mode 100644 index 0000000000..7f36651961 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/bootstrap.css @@ -0,0 +1,5785 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +@media print { + * { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="javascript:"]:after, + a[href^="#"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + select { + background: #fff !important; + } + .navbar { + display: none; + } + .table td, + .table th { + background-color: #fff !important; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 62.5%; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #428bca; + text-decoration: none; +} +a:hover, +a:focus { + color: #2a6496; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #999; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 200; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +cite { + font-style: normal; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-muted { + color: #999; +} +.text-primary { + color: #428bca; +} +a.text-primary:hover { + color: #3071a9; +} +.text-success { + color: #3c763d; +} +a.text-success:hover { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #428bca; +} +a.bg-primary:hover { + background-color: #3071a9; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #999; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +blockquote:before, +blockquote:after { + content: ""; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + white-space: nowrap; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: 0; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: 0; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: 0; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: 0; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: 0; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: 0; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: 0; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: 0; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + max-width: 100%; + background-color: transparent; +} +th { + text-align: left; +} +.table { + width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-child(odd) > td, +.table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover > td, +.table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +@media (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-x: scroll; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + /* IE8-9 */ + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eee; + opacity: 1; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +input[type="date"] { + line-height: 34px; +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + display: block; + min-height: 20px; + padding-left: 20px; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + display: inline; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +.radio[disabled], +.radio-inline[disabled], +.checkbox[disabled], +.checkbox-inline[disabled], +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"], +fieldset[disabled] .radio, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.has-feedback .form-control-feedback { + position: absolute; + top: 25px; + right: 0; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.form-control-static { + margin-bottom: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .control-label, +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +.form-horizontal .form-control-static { + padding-top: 7px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + top: 0; + right: 15px; +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus, +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + color: #333; + background-color: #ebebeb; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #428bca; + border-color: #357ebd; +} +.btn-primary:hover, +.btn-primary:focus, +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + color: #fff; + background-color: #3276b1; + border-color: #285e8e; +} +.btn-primary:active, +.btn-primary.active, +.open .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #428bca; + border-color: #357ebd; +} +.btn-primary .badge { + color: #428bca; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:hover, +.btn-success:focus, +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + color: #fff; + background-color: #47a447; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:hover, +.btn-info:focus, +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + color: #fff; + background-color: #39b3d7; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:hover, +.btn-warning:focus, +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ed9c28; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:hover, +.btn-danger:focus, +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + color: #fff; + background-color: #d2322d; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #428bca; + cursor: pointer; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #2a6496; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #999; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; + padding-right: 0; + padding-left: 0; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; +} +.collapse.in { + display: block; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition: height .35s ease; + transition: height .35s ease; +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\2a"; +} +.glyphicon-plus:before { + content: "\2b"; +} +.glyphicon-euro:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #428bca; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #999; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #999; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px solid; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus { + outline: none; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child > .btn:last-child, +.btn-group > .btn-group:first-child > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +[data-toggle="buttons"] > .btn > input[type="radio"], +[data-toggle="buttons"] > .btn > input[type="checkbox"] { + display: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #999; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #999; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #428bca; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #428bca; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + max-height: 340px; + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: none; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } + .navbar-nav.navbar-right:last-child { + margin-right: -15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + padding-left: 0; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + float: none; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-form.navbar-right:last-child { + margin-right: -15px; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } + .navbar-text.navbar-right:last-child { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #999; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #999; +} +.navbar-inverse .navbar-nav > li > a { + color: #999; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #999; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #999; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #999; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #428bca; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + color: #2a6496; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #fff; + cursor: default; + background-color: #428bca; + border-color: #428bca; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #999; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #999; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +.label[href]:hover, +.label[href]:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #999; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #808080; +} +.label-primary { + background-color: #428bca; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #3071a9; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #999; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +a.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #428bca; + background-color: #fff; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.container .jumbotron { + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; + } + .container .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #428bca; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable { + padding-right: 35px; +} +.alert-dismissable .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #428bca; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-size: 40px 40px; +} +.progress.active .progress-bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media, +.media .media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media-object { + display: block; +} +.media-heading { + margin: 0 0 5px; +} +.media > .pull-left { + margin-right: 10px; +} +.media > .pull-right { + margin-left: 10px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +a.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +a.list-group-item:focus { + text-decoration: none; + background-color: #f5f5f5; +} +a.list-group-item.active, +a.list-group-item.active:hover, +a.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #428bca; + border-color: #428bca; +} +a.list-group-item.active .list-group-item-heading, +a.list-group-item.active:hover .list-group-item-heading, +a.list-group-item.active:focus .list-group-item-heading { + color: inherit; +} +a.list-group-item.active .list-group-item-text, +a.list-group-item.active:hover .list-group-item-text, +a.list-group-item.active:focus .list-group-item-text { + color: #e1edf7; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +a.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +a.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +a.list-group-item-info.active:hover, +a.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +a.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +a.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table { + margin-bottom: 0; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + overflow: hidden; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse .panel-body { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #428bca; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #428bca; + border-color: #428bca; +} +.panel-primary > .panel-heading + .panel-collapse .panel-body { + border-top-color: #428bca; +} +.panel-primary > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #428bca; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-footer + .panel-collapse .panel-body { + border-bottom-color: #ebccd1; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + display: none; + overflow: auto; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -moz-transition: -moz-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: none; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + min-height: 16.42857143px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 20px; +} +.modal-footer { + padding: 19px 20px 20px; + margin-top: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1030; + display: block; + font-size: 12px; + line-height: 1.4; + visibility: visible; + filter: alpha(opacity=0); + opacity: 0; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + right: 5px; + bottom: 0; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1010; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + white-space: normal; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, .5) 0%), color-stop(rgba(0, 0, 0, .0001) 100%)); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, .0001) 0%), color-stop(rgba(0, 0, 0, .5) 100%)); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: none; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + margin-left: -10px; + font-family: serif; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + margin-left: -15px; + font-size: 30px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; + visibility: hidden !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/index.css b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/index.css new file mode 100644 index 0000000000..a7aa5a5aad --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/index.css @@ -0,0 +1,130 @@ +/* + Copyright (c) 2012-2019, Pierre-Olivier Latour + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * 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. + * The name of Pierre-Olivier Latour may not be used to endorse + or promote products derived from this software without specific + prior written permission. + + 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 PIERRE-OLIVIER LATOUR 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. + */ + +.row-file { + height: 40px; +} + +.column-icon { + width: 40px; + text-align: center; +} + +.column-name { +} + +.column-size { + width: 100px; + text-align: right; +} + +.column-move { + width: 40px; + text-align: center; +} + +.column-delete { + width: 40px; + text-align: center; +} + +.column-path { +} + +.column-progress { + width: 200px; +} + +.footer { + color: #999; + text-align: center; + font-size: 0.9em; +} + +#reload { + float: right; +} + +#create-input { + width: 50%; + height: 20px; +} + +#move-input { + width: 80%; + height: 20px; +} + +/* Bootstrap overrides */ + +.btn:focus { + outline: none; /* FIXME: Work around for Chrome only but still draws focus ring while button pressed */ +} + +.btn-toolbar { + margin-top: 30px; + margin-bottom: 20px; +} + +.table .progress { + margin-top: 0px; + margin-bottom: 0px; + height: 16px; +} + +.panel-default > .panel-heading { + color: #555; +} + +.breadcrumb { + background-color: transparent; + border-radius: 0px; + margin-bottom: 0px; + padding: 0px; +} + +.breadcrumb > .active { + color: #555; +} + +.breadcrumb > li + li:before { + color: #999; +} + +.table > tbody > tr > td { + vertical-align: middle; +} + +.table > tbody > tr > td > p { + margin: 0px; +} + +/* Initial state */ + +.uploading { + display: none; +} diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/jquery.fileupload.css b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/jquery.fileupload.css new file mode 100644 index 0000000000..fb6044d34f --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/css/jquery.fileupload.css @@ -0,0 +1,36 @@ +@charset "UTF-8"; +/* + * jQuery File Upload Plugin CSS 1.3.0 + * https://github.com/blueimp/jQuery-File-Upload + * + * Copyright 2013, Sebastian Tschan + * https://blueimp.net + * + * Licensed under the MIT license: + * http://www.opensource.org/licenses/MIT + */ + +.fileinput-button { + position: relative; + overflow: hidden; +} +.fileinput-button input { + position: absolute; + top: 0; + right: 0; + margin: 0; + opacity: 0; + -ms-filter: 'alpha(opacity=0)'; + font-size: 200px; + direction: ltr; + cursor: pointer; +} + +/* Fixes for IE < 8 */ +@media screen\9 { + .fileinput-button input { + filter: alpha(opacity=0); + font-size: 100%; + height: 100%; + } +} diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/en.lproj/Localizable.strings b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/en.lproj/Localizable.strings new file mode 100644 index 0000000000..2f4b4a24f2 --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/en.lproj/Localizable.strings @@ -0,0 +1,3 @@ +"PROLOGUE" = "

Drag & drop files on this window or use the \"Upload Files…\" button to upload new files.

"; +"EPILOGUE" = ""; +"FOOTER_FORMAT" = "%@ %@"; diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.eot b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.eot new file mode 100644 index 0000000000..4a4ca865d6 Binary files /dev/null and b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.eot differ diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.svg b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 0000000000..e3e2dc739d --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.ttf b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000..67fa00bf83 Binary files /dev/null and b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.ttf differ diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.woff b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.woff new file mode 100644 index 0000000000..8c54182aa5 Binary files /dev/null and b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/fonts/glyphicons-halflings-regular.woff differ diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/index.html b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/index.html new file mode 100644 index 0000000000..cc951f5b1e --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/index.html @@ -0,0 +1,196 @@ + + + + + + + + + %title% + + + + + + + + + + + + + + + + + +
+ + + + %prologue% + +
+ +
+ + + +
+ +
+
File Uploads in Progress
+
+
+ +
+
+ +
+
+
+ + %epilogue% + + + +
+ + + + + + + + + + + + + diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/bootstrap.min.js b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/bootstrap.min.js new file mode 100644 index 0000000000..b04a0e82ff --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/bootstrap.min.js @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); \ No newline at end of file diff --git a/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/html5shiv.min.js b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/html5shiv.min.js new file mode 100644 index 0000000000..448cebd79e --- /dev/null +++ b/pkg/apple/WebServer/GCDWebUploader/GCDWebUploader.bundle/Contents/Resources/js/html5shiv.min.js @@ -0,0 +1,8 @@ +/* + HTML5 Shiv v3.7.0 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +(function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); +a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; +c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| +"undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:"3.7.0",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f); +if(g)return a.createDocumentFragment();for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d= 1000000000) { + return (bytes / 1000000000).toFixed(2) + ' GB'; + } + if (bytes >= 1000000) { + return (bytes / 1000000).toFixed(2) + ' MB'; + } + return (bytes / 1000).toFixed(2) + ' KB'; +} + +function _showError(message, textStatus, errorThrown) { + $("#alerts").prepend(tmpl("template-alert", { + level: "danger", + title: (errorThrown != "" ? errorThrown : textStatus) + ": ", + description: message + })); +} + +function _disableReloads() { + _reloadingDisabled += 1; +} + +function _enableReloads() { + _reloadingDisabled -= 1; + + if (_pendingReloads.length > 0) { + _reload(_pendingReloads.shift()); + } +} + +function _reload(path) { + if (_reloadingDisabled) { + if ($.inArray(path, _pendingReloads) < 0) { + _pendingReloads.push(path); + } + return; + } + + _disableReloads(); + $.ajax({ + url: 'list', + type: 'GET', + data: {path: path}, + dataType: 'json' + }).fail(function(jqXHR, textStatus, errorThrown) { + _showError("Failed retrieving contents of \"" + path + "\"", textStatus, errorThrown); + }).done(function(data, textStatus, jqXHR) { + var scrollPosition = $(document).scrollTop(); + + if (path != _path) { + $("#path").empty(); + if (path == "/") { + $("#path").append('
  • ' + _device + '
  • '); + } else { + $("#path").append('
  • ' + _device + '
  • '); + var components = path.split("/").slice(1, -1); + for (var i = 0; i < components.length - 1; ++i) { + var subpath = "/" + components.slice(0, i + 1).join("/") + "/"; + $("#path").append('
  • ' + components[i] + '
  • '); + } + $("#path > li").click(function(event) { + _reload($(this).data("path")); + event.preventDefault(); + }); + $("#path").append('
  • ' + components[components.length - 1] + '
  • '); + } + _path = path; + } + + $("#listing").empty(); + for (var i = 0, file; file = data[i]; ++i) { + $(tmpl("template-listing", file)).data(file).appendTo("#listing"); + } + + $(".edit").editable(function(value, settings) { + var name = $(this).parent().parent().data("name"); + if (value != name) { + var path = $(this).parent().parent().data("path"); + $.ajax({ + url: 'move', + type: 'POST', + data: {oldPath: path, newPath: _path + value}, + dataType: 'json' + }).fail(function(jqXHR, textStatus, errorThrown) { + _showError("Failed moving \"" + path + "\" to \"" + _path + value + "\"", textStatus, errorThrown); + }).always(function() { + _reload(_path); + }); + } + return value; + }, { + onedit: function(settings, original) { + _disableReloads(); + }, + onsubmit: function(settings, original) { + _enableReloads(); + }, + onreset: function(settings, original) { + _enableReloads(); + }, + tooltip: 'Click to rename...' + }); + + $(".button-download").click(function(event) { + var path = $(this).parent().parent().data("path"); + setTimeout(function() { + window.location = "download?path=" + encodeURIComponent(path); + }, 0); + }); + + $(".button-open").click(function(event) { + var path = $(this).parent().parent().data("path"); + _reload(path); + }); + + $(".button-move").click(function(event) { + var path = $(this).parent().parent().data("path"); + if (path[path.length - 1] == "/") { + path = path.slice(0, path.length - 1); + } + $("#move-input").data("path", path); + $("#move-input").val(path); + $("#move-modal").modal("show"); + }); + + $(".button-delete").click(function(event) { + var path = $(this).parent().parent().data("path"); + $.ajax({ + url: 'delete', + type: 'POST', + data: {path: path}, + dataType: 'json' + }).fail(function(jqXHR, textStatus, errorThrown) { + _showError("Failed deleting \"" + path + "\"", textStatus, errorThrown); + }).always(function() { + _reload(_path); + }); + }); + + $(document).scrollTop(scrollPosition); + }).always(function() { + _enableReloads(); + }); +} + +$(document).ready(function() { + + // Workaround Firefox and IE not showing file selection dialog when clicking on "upload-file"