{{ $exception->getMessage() ?: 'Page Not Found' }}
16 |├── .env.example ├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── app ├── Console │ ├── Commands │ │ └── Inspire.php │ └── Kernel.php ├── Events │ └── Event.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Account │ │ │ ├── AccountController.php │ │ │ └── PreferencesController.php │ │ ├── Admin │ │ │ └── AdminController.php │ │ ├── Api │ │ │ └── UploadController.php │ │ ├── Auth │ │ │ ├── AuthController.php │ │ │ └── PasswordController.php │ │ ├── Controller.php │ │ ├── DownloadController.php │ │ └── IndexController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── AdminAuthenticate.php │ │ ├── ApiAuthenticate.php │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── RedirectIfAuthenticated.php │ │ └── VerifyCsrfToken.php │ ├── Requests │ │ └── Request.php │ ├── helpers.php │ └── routes.php ├── Jobs │ └── Job.php ├── Listeners │ └── .gitkeep ├── Models │ ├── Upload.php │ ├── User.php │ └── UserPreferences.php └── Providers │ ├── AppServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── artisan ├── bootstrap ├── app.php ├── autoload.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── compile.php ├── database.php ├── debugbar.php ├── filesystems.php ├── ide-helper.php ├── image.php ├── mail.php ├── queue.php ├── recaptcha.php ├── services.php ├── session.php ├── upste.php └── view.php ├── database ├── .gitignore ├── factories │ └── ModelFactory.php ├── migrations │ ├── .gitkeep │ ├── 2015_12_09_172957_create_users_table.php │ ├── 2015_12_09_173005_create_uploads_table.php │ ├── 2015_12_09_173022_create_password_resets_table.php │ ├── 2015_12_09_235053_fix_hash_length_for_uploads_table.php │ ├── 2015_12_11_221322_create_sessions_table.php │ ├── 2015_12_15_101257_create_jobs_table.php │ ├── 2015_12_15_101552_create_failed_jobs_table.php │ ├── 2016_02_20_173427_update_session_drivers.php │ ├── 2016_03_20_131515_add_original_hash.php │ ├── 2016_03_30_213843_increase_password_length.php │ ├── 2016_04_01_022122_add_view_counter.php │ ├── 2016_04_01_025729_rename_download.php │ ├── 2016_04_03_195138_create_preferences_table.php │ ├── 2016_04_03_213046_update_default_pagination.php │ └── 2016_07_26_153539_add_email_confirmation.php └── seeds │ ├── .gitkeep │ └── DatabaseSeeder.php ├── gulpfile.js ├── nginx.conf.example ├── package.json ├── phpspec.yml ├── phpunit.xml ├── public ├── .gitignore ├── .htaccess ├── assets │ ├── css │ │ └── .gitignore │ ├── img │ │ ├── favicon.png │ │ └── thumbnail.png │ └── js │ │ └── .gitignore ├── favicon.ico ├── index.php ├── robots.txt └── vendor │ └── folklore │ └── image │ ├── .gitkeep │ └── js │ └── image.js ├── readme.md ├── resources ├── assets │ ├── js │ │ ├── dropzone.js │ │ ├── global.js │ │ └── thumbnailhover.js │ └── sass │ │ ├── .gitignore │ │ ├── error.scss │ │ ├── global.scss │ │ └── thumbnailhover.scss ├── lang │ └── en │ │ ├── messages.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php └── views │ ├── account │ ├── api.blade.php │ ├── faq.blade.php │ ├── index.blade.php │ ├── preferences.blade.php │ ├── resources.blade.php │ ├── resources │ │ └── bash.blade.php │ └── uploads.blade.php │ ├── admin │ ├── index.blade.php │ ├── requests.blade.php │ ├── uploads.blade.php │ └── users.blade.php │ ├── auth │ ├── login.blade.php │ ├── password.blade.php │ ├── register.blade.php │ └── reset.blade.php │ ├── emails │ ├── admin │ │ ├── exception.blade.php │ │ └── new_registration.blade.php │ └── user │ │ ├── account_accepted.blade.php │ │ ├── account_confirmation.blade.php │ │ ├── account_rejected.blade.php │ │ ├── api_key_reset.blade.php │ │ └── password_reset.blade.php │ ├── errors │ ├── 404.blade.php │ └── 503.blade.php │ ├── index.blade.php │ ├── layouts │ ├── account.blade.php │ ├── admin.blade.php │ └── master.blade.php │ └── vendor │ ├── .gitkeep │ └── flash │ └── message.blade.php ├── server.php ├── storage ├── .gitignore ├── app │ ├── .gitignore │ ├── thumbnails │ │ └── .gitignore │ └── uploads │ │ └── .gitignore ├── debugbar │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore └── tests ├── ExampleTest.php └── TestCase.php /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | ## Make sure this is disabled for production or it will leak sensitive data. 3 | APP_DEBUG=false 4 | APP_KEY=SomeRandomString 5 | 6 | DB_CONNECTION=sqlite 7 | DB_HOST=localhost 8 | DB_DATABASE=homestead 9 | DB_USERNAME=homestead 10 | DB_PASSWORD=secret 11 | 12 | CACHE_DRIVER=file 13 | SESSION_DRIVER=file 14 | QUEUE_DRIVER=sync 15 | 16 | MAIL_DRIVER=smtp 17 | MAIL_HOST=mailtrap.io 18 | MAIL_PORT=2525 19 | MAIL_USERNAME=null 20 | MAIL_PASSWORD=null 21 | MAIL_ENCRYPTION=null 22 | MAIL_FROM=noreply@example.com 23 | 24 | ## Should cookies only be sent over a HTTPS connection? 25 | SECURE_COOKIES=false 26 | 27 | ## Which domains cookies are valid for. Should be set to .whateveryourdomainis.com 28 | COOKIE_DOMAIN=.example.com 29 | 30 | ## Bcrypt work factor. 10 is a good setting for most systems. 31 | PASSWORD_HASH_ROUNDS=10 32 | 33 | ## The name that this site will be referred to in emails and certain UI elements. 34 | SITE_NAME=uPste 35 | 36 | ## The url files are served from. 37 | UPLOAD_URL=https://a.example.com 38 | 39 | ## The initial length of upload slugs. 40 | ## This will increase as slugs become unavailable. 41 | UPLOAD_SLUG_LENGTH=3 42 | 43 | ## Sets the method for sending files to users. 44 | ## It is recommended to use one of 'x-accel' (nginx) or 'x-sendfile' (apache) 45 | ## See the included nginx.conf.example's /upload/ location for a working implementation. 46 | ## PRs are welcome for Apache implementations. 47 | ## Set to null to just use Laravel's Response::download() method. 48 | SENDFILE_METHOD=null 49 | 50 | ## Your email address. Used to send registration alerts and uncaught exceptions. 51 | OWNER_EMAIL=me@example.com 52 | 53 | ## Your name. 54 | OWNER_NAME=Me 55 | 56 | ## A link to public GPG key. Used to inform users how to securely contact you. 57 | OWNER_GPG=null 58 | 59 | ## Upload limit per file IN MEGABYTES. 60 | ## Please note that your web server AND php.ini have to support AT LEAST this amount. 61 | PER_UPLOAD_LIMIT=20 62 | 63 | ## Maximum amount of disk space a user can use IN MEGABYTES. Set to 0 for unlimited. 64 | USER_STORAGE_QUOTA=0 65 | 66 | ## Require an admin to manually activate user accounts before they can be used. 67 | REQUIRE_USER_APPROVAL=true 68 | 69 | ## Require users to verify their email via a link sent to their email address before the registration/request is processed. 70 | REQUIRE_EMAIL_VERIFICATION=false 71 | 72 | ## An IRC channel and server your users can contact you in. 73 | IRC_CHANNEL=null 74 | IRC_SERVER=null 75 | 76 | ## Attempt to remove EXIF tags from png and jpg images. 77 | ## This helps prevent accidental leaks of sensitive information like geolocation, 78 | ## but also modifies the uploaded files which may be undesirable. 79 | STRIP_EXIF=true 80 | 81 | ## Google recaptcha keys for registration. 82 | ## https://www.google.com/recaptcha 83 | ## Set either to null to disable recaptcha. 84 | RECAPTCHA_PUBLIC_KEY=null 85 | RECAPTCHA_PRIVATE_KEY=null 86 | 87 | ## None of these settings will be used unless you're using the redis driver for session, queue, or cache 88 | ## Redis connection scheme. Most people will be using either 'tcp' or 'unix' 89 | REDIS_SCHEME=tcp 90 | ## Ignored when REDIS_SCHEME is 'unix' 91 | REDIS_HOST=127.0.0.1 92 | REDIS_PORT=6379 93 | REDIS_PASSWORD=null 94 | ## Only used when REDIS_SCHEME is 'unix' 95 | REDIS_PATH=/tmp/redis.sock 96 | REDIS_DATABASE=0 97 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.less linguist-vendored 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /node_modules 3 | Homestead.yaml 4 | .env 5 | _ide_helper.php 6 | .phpstorm.meta.php 7 | _ide_helper_models.php 8 | public/.well-known 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Please view this file on the master branch, on stable branches it's out of date. 2 | 3 | v 1.2.3 4 | - [ENHANCEMENT] Add better file extension detection. 5 | 6 | v 1.2.2 7 | - [FIX] Account for email confirmation in user SELECTs. 8 | 9 | v 1.2.1 10 | - [FIX] Fix API error when no session is available. 11 | 12 | v 1.2.0 13 | - [FEATURE] Add option email confirmation on register. 14 | 15 | v 1.1.6 16 | - [FIX] Prevent an edge case with view caching involving two users having the same file hash. 17 | 18 | v 1.1.5 19 | - Make view cache file-specific. 20 | 21 | v 1.1.4 22 | - Cache client IPs on file view so views can't be spammed. 23 | 24 | v 1.1.3 25 | - Fix username apostrophe usage. 26 | 27 | v 1.1.2 28 | - Totally refactor caching so models pretty much handle their own caching with no outside interference. 29 | 30 | v 1.1.1 31 | - Clean up email subjects and fix routes when using daemon queue worker. 32 | 33 | v 1.1.0 34 | - Add deletion URLs for sharex compatibility. 35 | 36 | v 1.0.10 37 | - Fixed all the bugs in image previews. 38 | 39 | v 1.0.9 40 | - Add image previews on thumbnail hover. 41 | 42 | v 1.0.8 43 | - Switch to fit() for image resizing as it retains aspect ratio. 44 | 45 | v 1.0.7 46 | - Add bcrypt work factor to env 47 | - Add Honeypot service to further reduce spambots registering. 48 | 49 | v 1.0.6 50 | - Unify references to site under site\_name variable. 51 | - Hide updated\_at from uploads and rename getThumbnail() to getThumbnailUrl(). 52 | - Clean up admin/uploads a little. 53 | 54 | v 1.0.5 55 | - Show correct counts on admin user pages and update caches after saving a file. 56 | 57 | v 1.0.4 58 | - Don't call save() after create(), it's redundant. 59 | - Actually strip EXIF info from images and save them to the right file. 60 | 61 | v 1.0.3 62 | - Make the example nginx config a little safer. 63 | 64 | v 1.0.2 65 | - Add email to /u/preferences. 66 | - Fix broken view counter. 67 | - Make user created\_at timestamp use user timezone in /a/users. 68 | 69 | v 1.0.1 70 | - Fixed missing translation for deleting users. 71 | 72 | v 1.0.0 73 | - Started doing actual releases with versions. 74 | - Switched filesystem structure from uploads/$filename to uploads/md5(user\_id)/md5(first\_character\_of\_filename) to help performance on certain filesystems. 75 | 76 | v 1.0.0 Breaking Changes 77 | - Due to the directory structure change, you will be required to run `foreach (\App\Models\Upload::all() as $upload) { $upload->migrate(); }` in the `php artisan tinker` console immediately after upgrade in order for your uploads to be available again. It is recommended you make a backup of storage/app/ first. 78 | -------------------------------------------------------------------------------- /app/Console/Commands/Inspire.php: -------------------------------------------------------------------------------- 1 | comment(PHP_EOL . Inspiring::quote() . PHP_EOL); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Events/Event.php: -------------------------------------------------------------------------------- 1 | shouldReport($e) && php_sapi_name() != 'cli') { 41 | $data = [ 42 | 'ip' => request()->getClientIp(), 43 | 'url' => request()->fullUrl(), 44 | 'exception' => $e->__toString(), 45 | ]; 46 | 47 | Mail::queue(['text' => 'emails.admin.exception'], $data, function (Message $message) use ($data) { 48 | $message->subject('Application Exception'); 49 | $message->to(config('upste.owner_email')); 50 | }); 51 | } 52 | 53 | return parent::report($e); 54 | } 55 | 56 | /** 57 | * Render an exception into an HTTP response. 58 | * 59 | * @param \Illuminate\Http\Request $request 60 | * @param \Exception $e 61 | * @return \Illuminate\Http\Response 62 | */ 63 | public function render($request, Exception $e) 64 | { 65 | if ($e instanceof TokenMismatchException) { 66 | flash()->error('CSRF verification failed, try logging in again.')->important(); 67 | Auth::logout(); 68 | 69 | return redirect()->route('login'); 70 | } 71 | 72 | if ($e instanceof MethodNotAllowedHttpException && $request->getMethod() == 'GET') { 73 | flash()->error('That URL is for POST requests only.'); 74 | 75 | return redirect()->route('account'); 76 | } 77 | 78 | return parent::render($request, $e); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/Http/Controllers/Account/AccountController.php: -------------------------------------------------------------------------------- 1 | view('account.resources.bash')->header('Content-Type', 'text/plain'); 36 | } 37 | 38 | public function getUploads(Request $request) 39 | { 40 | $uploads = $request->user()->uploads()->orderBy('created_at', 'desc')->paginate($request->user()->preferences->pagination_items); 41 | 42 | return view('account.uploads', compact('uploads')); 43 | } 44 | 45 | public function deleteUpload(Request $request, Upload $upload) 46 | { 47 | if ($request->user()->id !== $upload->user_id && !$request->user()->isPrivilegedUser()) { 48 | return abort(StatusCode::NOT_FOUND); 49 | } 50 | 51 | $upload->forceDelete(); 52 | flash()->success(trans('messages.file_deleted', ['name' => $upload->original_name])); 53 | 54 | return redirect()->route('account.uploads'); 55 | } 56 | 57 | public function deleteAllUploads(Request $request) 58 | { 59 | $uploads = $request->user()->uploads()->get(); 60 | $uploadIds = []; 61 | foreach ($uploads as $upload) { 62 | $upload->deleteDirs(); 63 | $upload->invalidateCache(); 64 | $uploadIds[] = $upload->id; 65 | } 66 | 67 | DB::table('uploads')->whereIn('id', $uploadIds)->delete(); 68 | 69 | $request->user()->invalidateCache(); 70 | 71 | flash()->success(trans('messages.all_uploads_deleted')); 72 | 73 | return redirect()->route('account.uploads'); 74 | } 75 | 76 | public function postResetKey(Request $request) 77 | { 78 | do { 79 | $newKey = str_random(Helpers::API_KEY_LENGTH); 80 | } while (User::whereApikey($newKey)->first()); 81 | 82 | $user = $request->user(); 83 | $user->fill(['apikey' => $newKey])->save(); 84 | flash()->success(trans('messages.api_key_changed', ['api_key' => $newKey]))->important(); 85 | 86 | $passwordRoute = route('account.password.email'); 87 | Mail::queue(['text' => 'emails.user.api_key_reset'], compact('user', 'passwordRoute'), function (Message $message) use ($user) { 88 | $message->subject('API Key Reset'); 89 | $message->to($user->email); 90 | }); 91 | 92 | return redirect()->route('account'); 93 | } 94 | 95 | public function getThumbnail(Request $request, Upload $upload) 96 | { 97 | if (!$request->user() || $request->user()->id !== $upload->user_id && !$request->user()->isPrivilegedUser()) { 98 | return abort(StatusCode::NOT_FOUND); 99 | } 100 | 101 | return response()->download($upload->getThumbnailPath(true), 'thumbnail.' . $upload->name); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/Http/Controllers/Account/PreferencesController.php: -------------------------------------------------------------------------------- 1 | $request->user()->preferences]); 16 | } 17 | 18 | public function post(Request $request) 19 | { 20 | $rules = [ 21 | 'timezone' => 'required|timezone', 22 | 'pagination-items' => 'required|min:4|max:50|integer', 23 | ]; 24 | 25 | $validator = Validator::make($request->all(), $rules); 26 | 27 | if ($validator->fails()) { 28 | return redirect()->route('account.preferences') 29 | ->withErrors($validator); 30 | } 31 | 32 | $request->user()->preferences->fill([ 33 | 'user_id' => Auth::id(), 34 | 'timezone' => $request->input('timezone'), 35 | 'pagination_items' => $request->input('pagination-items'), 36 | ])->save(); 37 | 38 | flash()->success(trans('messages.preferences_saved')); 39 | 40 | return redirect()->route('account.preferences'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/AdminController.php: -------------------------------------------------------------------------------- 1 | whereConfirmed(true)->count(); 19 | view()->share('requestCount', $requestCount); 20 | } 21 | } 22 | 23 | public function getIndex() 24 | { 25 | if (config('upste.require_user_approval')) { 26 | return redirect()->route('admin.requests'); 27 | } else { 28 | return redirect()->route('admin.users'); 29 | } 30 | } 31 | 32 | public function getRequests(Request $request) 33 | { 34 | $users = User::whereEnabled(false)->whereConfirmed(true)->orderBy('created_at', 'asc')->paginate($request->user()->preferences->pagination_items); 35 | 36 | return view('admin.requests', compact('users')); 37 | } 38 | 39 | public function getUsers(Request $request) 40 | { 41 | $users = User::whereEnabled(true)->whereConfirmed(true)->paginate($request->user()->preferences->pagination_items); 42 | return view('admin.users', compact('users')); 43 | } 44 | 45 | public function postUserBan(User $user) 46 | { 47 | if ($user->isSuperUser()) { 48 | flash()->error(trans('messages.admin.failed_superuser_action', ['type' => 'ban'])); 49 | 50 | return redirect()->route('admin.users'); 51 | } 52 | 53 | $user->fill(['banned' => true])->save(); 54 | flash()->success(trans('messages.admin.banned_user', ['name' => $user->name])); 55 | 56 | return redirect()->route('admin.users'); 57 | } 58 | 59 | public function postUserDelete(User $user) 60 | { 61 | if ($user->isSuperUser()) { 62 | flash()->error(trans('messages.admin.failed_superuser_action', ['type' => 'delete'])); 63 | 64 | return redirect()->route('admin.users'); 65 | } 66 | 67 | $user->forceDelete(); 68 | flash()->success(trans('messages.admin.deleted_user', ['name' => $user->name])); 69 | 70 | return redirect()->route('admin.users'); 71 | } 72 | 73 | public function postUserUnban(User $user) 74 | { 75 | $user->fill(['banned' => false])->save(); 76 | flash()->success(trans('messages.admin.unbanned_user', ['name' => $user->name])); 77 | 78 | return redirect()->route('admin.users'); 79 | } 80 | 81 | public function getUploads(Request $request, User $user) 82 | { 83 | $allUploads = $user->uploads(); 84 | $uploads = $allUploads->orderBy('created_at', 'desc')->paginate($request->user()->preferences->pagination_items); 85 | 86 | return view('admin.uploads', compact('uploads', 'user')); 87 | } 88 | 89 | public function postUserAccept(User $user) 90 | { 91 | $user->enabled = true; 92 | $user->save(); 93 | 94 | $loginRoute = route('login'); 95 | Mail::queue(['text' => 'emails.user.account_accepted'], compact('user', 'loginRoute'), function (Message $message) use ($user) { 96 | $message->subject('Account Request Accepted'); 97 | $message->to($user->email); 98 | }); 99 | 100 | flash()->success(trans('messages.admin.account_accepted', ['name' => $user->name])); 101 | 102 | return redirect()->route('admin.users'); 103 | } 104 | 105 | public function postUserReject(User $user) 106 | { 107 | Mail::queue(['text' => 'emails.user.account_rejected'], compact('user'), function (Message $message) use ($user) { 108 | $message->subject('Account Request Rejected'); 109 | $message->to($user->email); 110 | }); 111 | 112 | $user->forceDelete(); 113 | flash()->success(trans('messages.admin.account_rejected', ['name' => $user->name])); 114 | 115 | return redirect()->route('admin.users'); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/UploadController.php: -------------------------------------------------------------------------------- 1 | hasFile('file')) { 24 | return response()->json([trans('messages.upload_file_not_found')], StatusCode::BAD_REQUEST); 25 | } 26 | 27 | $uploadedFile = $request->file('file'); 28 | if (!$uploadedFile->isValid()) { 29 | return response()->json([trans('messages.invalid_file_upload')], StatusCode::BAD_REQUEST); 30 | } 31 | 32 | if ($uploadedFile->getSize() >= config('upste.upload_limit')) { 33 | $responseMsg = trans('messages.upload_too_large', ['limit' => Helpers::formatBytes(config('upste.upload_limit'))]); 34 | return response()->json([$responseMsg], StatusCode::REQUEST_ENTITY_TOO_LARGE); 35 | } 36 | 37 | // If this upload would hit the quota defined in .env, reject it. 38 | if (config('upste.user_storage_quota') > 0 && !$request->user()->isPrivilegedUser() && ($request->user()->getUploadsSize() + $uploadedFile->getSize()) >= config('upste.user_storage_quota')) { 39 | $responseMsg = trans('messages.reached_upload_limit', ['limit' => Helpers::formatBytes(config('upste.user_storage_quota'))]); 40 | return response()->json([$responseMsg], StatusCode::FORBIDDEN); 41 | } 42 | 43 | $ext = Helpers::detectFileExtension($uploadedFile); 44 | $originalHash = sha1_file($uploadedFile); 45 | $originalName = $uploadedFile->getClientOriginalName(); 46 | 47 | // Check to see if we already have this file for this user. 48 | $existing = Upload::whereOriginalHash($originalHash)->whereUserId($request->user()->id)->first(); 49 | if ($existing) { 50 | $result = [ 51 | 'url' => route('files.get', $existing), 52 | 'delete_url' => route('account.uploads.delete', $existing), 53 | ]; 54 | 55 | $existing->original_name = $originalName; 56 | $existing->save(); 57 | 58 | return response()->json($result, StatusCode::CREATED)->setJsonOptions(JSON_UNESCAPED_SLASHES); 59 | } 60 | 61 | $randomLen = config('upste.upload_slug_length'); 62 | do { 63 | $newName = str_random($randomLen++) . ".$ext"; 64 | } while (Upload::whereName($newName)->first() || $newName === 'index.php'); 65 | 66 | $upload = new Upload([ 67 | 'user_id' => $request->user()->id, 68 | 'name' => $newName, 69 | 'original_name' => $originalName, 70 | 'original_hash' => $originalHash 71 | ]); 72 | 73 | $uploadFileHandle = fopen($uploadedFile->getRealPath(), 'rb'); 74 | Storage::put($upload->getPath(), $uploadFileHandle); 75 | fclose($uploadFileHandle); 76 | 77 | if (Helpers::isImage($uploadedFile)) { 78 | try { 79 | $img = Image::make($uploadedFile); 80 | } catch (NotReadableException $ex) { 81 | Log::error($ex); 82 | 83 | return response()->json([trans('messages.could_not_read_image')], StatusCode::INTERNAL_SERVER_ERROR); 84 | } catch (NotSupportedException $ex) { 85 | Log::error($ex); 86 | 87 | return response()->json([trans('messages.unsupported_image_type')], StatusCode::INTERNAL_SERVER_ERROR); 88 | } 89 | 90 | try { 91 | $upload->createDirs(); 92 | $img->backup(); 93 | $img->fit(128, 128)->save($upload->getThumbnailPath(true)); 94 | $img->reset(); 95 | 96 | if (Helpers::shouldStripExif($uploadedFile)) { 97 | $img->save($upload->getPath(true)); 98 | } 99 | } catch (NotWritableException $ex) { 100 | Log::error($ex); 101 | $upload->deleteDirs(); 102 | 103 | return response()->json([trans('messages.could_not_write_image')], StatusCode::INTERNAL_SERVER_ERROR); 104 | } finally { 105 | $img->destroy(); 106 | } 107 | } 108 | 109 | $savedFile = $upload->getPath(true); 110 | $upload->hash = sha1_file($savedFile); 111 | $upload->size = filesize($savedFile); 112 | 113 | $upload->save(); 114 | 115 | $result = [ 116 | 'url' => route('files.get', $upload), 117 | 'delete_url' => route('account.uploads.delete', $upload), 118 | ]; 119 | 120 | return response()->json($result, StatusCode::CREATED)->setJsonOptions(JSON_UNESCAPED_SLASHES); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordController.php: -------------------------------------------------------------------------------- 1 | redirectTo = route('account'); 36 | } 37 | 38 | /** 39 | * Send a reset link to the given user. 40 | * 41 | * @param \Illuminate\Http\Request $request 42 | * @return \Illuminate\Http\Response 43 | */ 44 | public function sendResetLinkEmail(Request $request) 45 | { 46 | $rules = [ 47 | 'email' => 'required|email', 48 | ]; 49 | 50 | if (config('upste.recaptcha_enabled')) { 51 | $rules['g-recaptcha-response'] = 'required|recaptcha'; 52 | } 53 | 54 | $this->validate($request, $rules); 55 | 56 | $broker = $this->getBroker(); 57 | 58 | $passwordRoute = route('account.password.reset'); 59 | view()->composer('emails.user.password_reset', function ($view) use ($passwordRoute) { 60 | $view->with(compact('passwordRoute')); 61 | }); 62 | 63 | $response = Password::broker($broker)->sendResetLink($request->only('email'), function (Message $message) { 64 | $message->subject($this->getEmailSubject()); 65 | }); 66 | 67 | switch ($response) { 68 | case Password::RESET_LINK_SENT: 69 | return $this->getSendResetLinkEmailSuccessResponse($response); 70 | 71 | case Password::INVALID_USER: 72 | default: 73 | return $this->getSendResetLinkEmailFailureResponse($response); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | getPath())) { 20 | if ($upload->user->banned) { 21 | Log::info('Refusing to serve file for banned user.', ['user' => $upload->user->name, 'file' => $upload->name]); 22 | return abort(StatusCode::NOT_FOUND); 23 | } 24 | 25 | if (!$request->user() || $request->user()->id !== $upload->user_id) { 26 | $cacheKey = sprintf('cached_view:%s:%s', $request->ip(), $upload->name); 27 | if (!Cache::has($cacheKey)) { 28 | Cache::put($cacheKey, 1, 60); 29 | DB::table('uploads')->where('id', $upload->id)->increment('views'); 30 | } 31 | } 32 | 33 | return Helpers::sendFile($upload); 34 | } 35 | 36 | return abort(StatusCode::NOT_FOUND); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Http/Controllers/IndexController.php: -------------------------------------------------------------------------------- 1 | route('account'); 21 | } 22 | 23 | return view('index'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | ApiAuthenticate::class, 41 | 'admin' => AdminAuthenticate::class, 42 | 'auth' => Authenticate::class, 43 | 'auth.basic' => AuthenticateWithBasicAuth::class, 44 | 'guest' => RedirectIfAuthenticated::class, 45 | ]; 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Middleware/AdminAuthenticate.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 29 | } 30 | 31 | /** 32 | * Handle an incoming request. 33 | * 34 | * @param \Illuminate\Http\Request $request 35 | * @param \Closure $next 36 | * @return mixed 37 | */ 38 | public function handle(Request $request, Closure $next) 39 | { 40 | if (!$this->auth->check()) { 41 | flash()->error('You must log in to access that page.'); 42 | 43 | return redirect()->route('login'); 44 | } 45 | 46 | if (!$request->user()->isPrivilegedUser()) { 47 | flash()->error('You do not have permission to access that area.'); 48 | 49 | return redirect()->route('index'); 50 | } 51 | 52 | return $next($request); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Http/Middleware/ApiAuthenticate.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 30 | } 31 | 32 | /** 33 | * Handle an incoming request. 34 | * 35 | * @param \Illuminate\Http\Request $request 36 | * @param \Closure $next 37 | * @return mixed 38 | */ 39 | public function handle(Request $request, Closure $next) 40 | { 41 | if (!$request->has('key')) { 42 | return response()->json(['missing_api_key'], StatusCode::UNAUTHORIZED); 43 | } 44 | 45 | $apiKey = $request->input('key'); 46 | $user = User::whereApikey($apiKey)->first(); 47 | if (!$user) { 48 | return response()->json(['invalid_api_key'], StatusCode::UNAUTHORIZED); 49 | } 50 | 51 | if (config('upste.require_email_verification') && !$user->confirmed) { 52 | return response()->json(['email_not_confirmed'], StatusCode::UNAUTHORIZED); 53 | } 54 | 55 | if (config('upste.require_user_approval') && !$user->enabled) { 56 | return response()->json(['account_not_approved'], StatusCode::UNAUTHORIZED); 57 | } 58 | 59 | if ($user->banned) { 60 | return response()->json(['user_banned'], StatusCode::UNAUTHORIZED); 61 | } 62 | 63 | Auth::onceUsingId($user->id); 64 | 65 | return $next($request); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 30 | } 31 | 32 | /** 33 | * Handle an incoming request. 34 | * 35 | * @param \Illuminate\Http\Request $request 36 | * @param \Closure $next 37 | * @return mixed 38 | */ 39 | public function handle(Request $request, Closure $next) 40 | { 41 | if (!$this->auth->check()) { 42 | Auth::logout(); 43 | Session::flush(); 44 | flash()->error(trans('messages.not_logged_in'))->important(); 45 | 46 | return redirect()->route('login'); 47 | } 48 | 49 | if (config('upste.require_email_verification') && !$request->user()->confirmed) { 50 | Auth::logout(); 51 | Session::flush(); 52 | flash()->error(trans('messages.not_confirmed'))->important(); 53 | 54 | return redirect()->route('login'); 55 | } 56 | 57 | if (config('upste.require_user_approval') && !$request->user()->enabled) { 58 | Auth::logout(); 59 | Session::flush(); 60 | flash()->error(trans('messages.not_activated'))->important(); 61 | 62 | return redirect()->route('login'); 63 | } 64 | 65 | if ($request->user()->banned) { 66 | Auth::logout(); 67 | Session::flush(); 68 | flash()->error(trans('messages.not_activated'))->important(); 69 | 70 | return redirect()->route('login'); 71 | } 72 | 73 | return $next($request); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 27 | } 28 | 29 | /** 30 | * Handle an incoming request. 31 | * 32 | * @param \Illuminate\Http\Request $request 33 | * @param \Closure $next 34 | * @return mixed 35 | */ 36 | public function handle(Request $request, Closure $next) 37 | { 38 | if ($this->auth->check()) { 39 | return redirect()->route('account'); 40 | } 41 | 42 | return $next($request); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | make()->header('X-Accel-Redirect', '/' . $upload->getPath()) 31 | ->header('Content-Type', '') 32 | ->header('Content-Disposition', sprintf('inline; filename="%s"', $upload->original_name)); 33 | break; 34 | case 'x-sendfile': 35 | return response()->make()->header('X-Sendfile', $upload->getPath(true)) 36 | ->header('Content-Type', '') 37 | ->header('Content-Disposition', sprintf('inline; filename="%s"', $upload->original_name)); 38 | break; 39 | default: 40 | return response()->file($upload->getPath(true), ['Content-Disposition' => sprintf('inline; filename="%s"', $upload->original_name)]); 41 | } 42 | } 43 | 44 | // http://stackoverflow.com/questions/2510434/format-bytes-to-kilobytes-megabytes-gigabytes 45 | /** 46 | * @param $bytes 47 | * @param int $precision 48 | * @return string 49 | */ 50 | public static function formatBytes($bytes, $precision = 2) 51 | { 52 | $units = ['B', 'KB', 'MB', 'GB', 'TB']; 53 | 54 | $bytes = max($bytes, 0); 55 | $pow = floor(($bytes ? log($bytes) : 0) / log(1000)); 56 | $pow = min($pow, count($units) - 1); 57 | $bytes /= pow(1000, $pow); 58 | 59 | return round($bytes, $precision) . ' ' . $units[$pow]; 60 | } 61 | 62 | /** 63 | * Checks whether or not it's safe to attempt to strip exif tags from a file 64 | * 65 | * @param $file 66 | * @return bool 67 | */ 68 | public static function shouldStripExif(UploadedFile $file) 69 | { 70 | if (!config('upste.strip_exif')) { 71 | return false; 72 | } 73 | 74 | if (function_exists('exif_imagetype')) { 75 | try { 76 | switch (exif_imagetype($file)) { 77 | case IMAGETYPE_JPEG: 78 | case IMAGETYPE_PNG: 79 | return true; 80 | break; 81 | default: 82 | return false; 83 | break; 84 | } 85 | } catch (Exception $ex) { 86 | Log::error($ex->getMessage()); 87 | 88 | return false; 89 | } 90 | } 91 | 92 | return false; 93 | } 94 | 95 | public static function isImage(UploadedFile $file) 96 | { 97 | if (function_exists('exif_imagetype')) { 98 | try { 99 | switch (exif_imagetype($file)) { 100 | case IMAGETYPE_JPEG: 101 | case IMAGETYPE_PNG: 102 | case IMAGETYPE_GIF: 103 | return true; 104 | break; 105 | default: 106 | return false; 107 | break; 108 | } 109 | } catch (Exception $ex) { 110 | Log::error($ex); 111 | 112 | return false; 113 | } 114 | } 115 | 116 | return false; 117 | } 118 | 119 | public static function properize($string) 120 | { 121 | return $string . '\'' . (ends_with($string, 's') ? '' : 's'); 122 | } 123 | 124 | public static function detectFileExtension(UploadedFile $file) 125 | { 126 | if ($file->getClientOriginalExtension()) { 127 | return $file->getClientOriginalExtension(); 128 | } 129 | 130 | $mimeTypes = new MimeTypes; 131 | $extension = $mimeTypes->getExtension(mime_content_type($file->getRealPath())); 132 | if ($extension) { 133 | if ($extension == 'jpeg') { 134 | return 'jpg'; 135 | } 136 | 137 | return $extension; 138 | } 139 | 140 | return 'txt'; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /app/Http/routes.php: -------------------------------------------------------------------------------- 1 | 'guest'], function () { 18 | Route::get('/', [ 19 | 'as' => 'index', 'uses' => 'IndexController@getIndex']); 20 | 21 | Route::get('login', [ 22 | 'as' => 'login', 'uses' => 'Auth\AuthController@getLogin']); 23 | 24 | Route::post('login', [ 25 | 'as' => 'login', 'uses' => 'Auth\AuthController@postLogin']); 26 | 27 | Route::get('register', [ 28 | 'as' => 'register', 'uses' => 'Auth\AuthController@getRegister']); 29 | 30 | Route::post('register', [ 31 | 'as' => 'register', 'uses' => 'Auth\AuthController@postRegister']); 32 | 33 | if (config('upste.require_email_verification')) { 34 | Route::get('register/verify/{confirmationCode}', [ 35 | 'as' => 'register.confirmation', 'uses' => 'Auth\AuthController@confirm' 36 | ]); 37 | } 38 | 39 | Route::group(['prefix' => 'password'], function () { 40 | Route::get('email', [ 41 | 'as' => 'account.password.email', 'uses' => 'Auth\PasswordController@getEmail']); 42 | 43 | Route::post('email', [ 44 | 'as' => 'account.password.email', 'uses' => 'Auth\PasswordController@postEmail']); 45 | 46 | Route::get('reset/{token}', [ 47 | 'as' => 'account.password.reset', 'uses' => 'Auth\PasswordController@getReset']); 48 | 49 | Route::post('reset', [ 50 | 'as' => 'account.password.reset', 'uses' => 'Auth\PasswordController@postReset']); 51 | }); 52 | }); 53 | 54 | /* 55 | * Routes only authenticated users can access 56 | */ 57 | Route::group(['middleware' => 'auth', 'prefix' => 'u'], function () { 58 | Route::get('/', [ 59 | 'as' => 'account', 'uses' => 'Account\AccountController@getindex']); 60 | 61 | Route::get('logout', [ 62 | 'as' => 'logout', 'uses' => 'Auth\AuthController@getLogout']); 63 | 64 | Route::get('resources', [ 65 | 'as' => 'account.resources', 'uses' => 'Account\AccountController@getResources']); 66 | 67 | Route::get('resources/scripts/bash', [ 68 | 'as' => 'account.resources.bash', 'uses' => 'Account\AccountController@getBashScript']); 69 | 70 | Route::get('faq', [ 71 | 'as' => 'account.faq', 'uses' => 'Account\AccountController@getFaq']); 72 | 73 | Route::get('preferences', [ 74 | 'as' => 'account.preferences', 'uses' => 'Account\PreferencesController@get']); 75 | 76 | Route::post('preferences', [ 77 | 'as' => 'account.preferences', 'uses' => 'Account\PreferencesController@post']); 78 | 79 | Route::group(['prefix' => 'uploads'], function () { 80 | Route::get('/', [ 81 | 'as' => 'account.uploads', 'uses' => 'Account\AccountController@getUploads']); 82 | 83 | Route::post('/delete', [ 84 | 'as' => 'account.uploads.delete_all', 'uses' => 'Account\AccountController@deleteAllUploads']); 85 | 86 | Route::post('{upload}/delete', [ 87 | 'as' => 'account.uploads.delete', 'uses' => 'Account\AccountController@deleteUpload']); 88 | 89 | Route::get('{upload}/delete', [ 90 | 'as' => 'account.uploads.delete', 'uses' => 'Account\AccountController@deleteUpload']); 91 | 92 | Route::get('{upload}/thumbnail', [ 93 | 'as' => 'account.uploads.thumbnail', 'uses' => 'Account\AccountController@getThumbnail']); 94 | }); 95 | 96 | Route::post('resetkey', [ 97 | 'as' => 'account.resetkey', 'uses' => 'Account\AccountController@postResetKey']); 98 | }); 99 | 100 | /* 101 | * Routes only admins can access 102 | */ 103 | Route::group(['middleware' => 'admin', 'prefix' => 'a'], function () { 104 | 105 | Route::get('/', [ 106 | 'as' => 'admin', 'uses' => 'Admin\AdminController@getIndex']); 107 | 108 | Route::group(['prefix' => 'users'], function () { 109 | Route::get('/', [ 110 | 'as' => 'admin.users', 'uses' => 'Admin\AdminController@getUsers']); 111 | 112 | Route::post('{user}/ban', [ 113 | 'as' => 'admin.users.ban', 'uses' => 'Admin\AdminController@postUserBan']); 114 | 115 | Route::post('{user}/unban', [ 116 | 'as' => 'admin.users.unban', 'uses' => 'Admin\AdminController@postUserUnban']); 117 | 118 | Route::get('{user}/uploads', [ 119 | 'as' => 'admin.users.uploads', 'uses' => 'Admin\AdminController@getUploads']); 120 | 121 | Route::post('{user}/delete', [ 122 | 'as' => 'admin.users.delete', 'uses' => 'Admin\AdminController@postUserDelete']); 123 | 124 | if (config('upste.require_user_approval')) { 125 | Route::get('requests', [ 126 | 'as' => 'admin.requests', 'uses' => 'Admin\AdminController@getRequests']); 127 | 128 | Route::post('{user}/accept', [ 129 | 'as' => 'admin.users.accept', 'uses' => 'Admin\AdminController@postUserAccept']); 130 | 131 | Route::post('{user}/reject', [ 132 | 'as' => 'admin.users.reject', 'uses' => 'Admin\AdminController@postUserReject']); 133 | } 134 | }); 135 | }); 136 | 137 | /* 138 | * API routes 139 | */ 140 | Route::group(['middleware' => 'api', 'prefix' => 'api'], function () { 141 | Route::post('upload', [ 142 | 'as' => 'api.upload', 'uses' => 'Api\UploadController@post']); 143 | }); 144 | 145 | /* 146 | * Uploads 147 | */ 148 | Route::group(['domain' => config('upste.upload_domain')], function () { 149 | Route::get('{upload}', [ 150 | 'as' => 'files.get', 'uses' => 'DownloadController@get']); 151 | }); 152 | -------------------------------------------------------------------------------- /app/Jobs/Job.php: -------------------------------------------------------------------------------- 1 | sum('size'); 57 | }); 58 | } 59 | 60 | public static function count() 61 | { 62 | return Cache::rememberForever('uploads', function () { 63 | return DB::table('uploads')->count(); 64 | }); 65 | } 66 | 67 | public function save(array $options = []) 68 | { 69 | $this->invalidateCache(); 70 | $this->user->invalidateCache(); 71 | 72 | return parent::save($options); 73 | } 74 | 75 | public function invalidateCache() 76 | { 77 | Cache::forget('uploads'); 78 | Cache::forget('uploads_total_size'); 79 | } 80 | 81 | /** 82 | * Get the user that owns the upload. 83 | */ 84 | public function user() 85 | { 86 | return $this->belongsTo(User::class); 87 | } 88 | 89 | public function forceDelete() 90 | { 91 | $this->deleteDirs(); 92 | $this->invalidateCache(); 93 | $this->user->invalidateCache(); 94 | 95 | return parent::forceDelete(); 96 | } 97 | 98 | public function deleteDirs() 99 | { 100 | $path = $this->getPath(); 101 | $dir = $this->getDir(); 102 | if (Storage::exists($path)) { 103 | Storage::delete($path); 104 | if (!count(Storage::files($dir))) { 105 | Storage::deleteDir($dir); 106 | } 107 | } 108 | 109 | $path = $this->getThumbnailPath(); 110 | $dir = $this->getThumbnailDir(); 111 | if (Storage::exists($path)) { 112 | Storage::delete($path); 113 | if (!count(Storage::files($dir))) { 114 | Storage::deleteDir($dir); 115 | } 116 | } 117 | } 118 | 119 | public function getPath($fullPath = false) 120 | { 121 | return $this->getDir($fullPath) . $this->name; 122 | } 123 | 124 | public function getDir($fullDir = false) 125 | { 126 | if ($fullDir) { 127 | return storage_path(sprintf('app/uploads/%s/%s/', md5($this->user_id), md5(mb_substr($this->name, 0, 1, 'utf-8')))); 128 | } 129 | 130 | return sprintf('uploads/%s/%s/', md5($this->user_id), md5(mb_substr($this->name, 0, 1, 'utf-8'))); 131 | } 132 | 133 | public function getThumbnailPath($fullPath = false) 134 | { 135 | return $this->getThumbnailDir($fullPath) . $this->name; 136 | } 137 | 138 | public function getThumbnailDir($fullDir = false) 139 | { 140 | if ($fullDir) { 141 | return storage_path(sprintf('app/thumbnails/%s/%s/', md5($this->user_id), md5(mb_substr($this->name, 0, 1, 'utf-8')))); 142 | } 143 | 144 | return sprintf('thumbnails/%s/%s/', md5($this->user_id), md5(mb_substr($this->name, 0, 1, 'utf-8'))); 145 | } 146 | 147 | /** 148 | * Migrates files from the old uploads/$filename structure to a more efficient one. 149 | * Should only be called once from the command line. 150 | */ 151 | public function migrate() 152 | { 153 | if (php_sapi_name() !== 'cli') { 154 | trigger_error('CLI-only (Upload#migrate()) function called outside of CLI SAPI.', E_USER_ERROR); 155 | 156 | return; 157 | } 158 | 159 | $this->createDirs(); 160 | 161 | if (Storage::exists('uploads/' . $this->name)) { 162 | Storage::move('uploads/' . $this->name, $this->getPath()); 163 | } 164 | 165 | if (Storage::exists('thumbnails/' . $this->name)) { 166 | Storage::move('thumbnails/' . $this->name, $this->getThumbnailPath()); 167 | } 168 | } 169 | 170 | public function createDirs() 171 | { 172 | if (!Storage::exists($this->getDir())) { 173 | Storage::makeDirectory($this->getDir()); 174 | } 175 | 176 | if (!Storage::exists($this->getThumbnailDir())) { 177 | Storage::makeDirectory($this->getThumbnailDir()); 178 | } 179 | } 180 | 181 | public function hasPreview() 182 | { 183 | return $this->getThumbnailUrl() !== elixir('assets/img/thumbnail.png'); 184 | } 185 | 186 | public function getThumbnailUrl() 187 | { 188 | if (Storage::exists($this->getThumbnailDir() . $this->name)) { 189 | return route('account.uploads.thumbnail', $this); 190 | } 191 | 192 | return elixir('assets/img/thumbnail.png'); 193 | } 194 | 195 | public function getRouteKeyName() 196 | { 197 | return 'name'; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | where('enabled', true)->where('confirmed', true)->count(); 74 | }); 75 | } 76 | 77 | /** 78 | * Get this user's uploads. 79 | */ 80 | public function uploads() 81 | { 82 | return $this->hasMany(Upload::class); 83 | } 84 | 85 | public function preferences() 86 | { 87 | return $this->hasOne(UserPreferences::class); 88 | } 89 | 90 | public function forceDelete() 91 | { 92 | foreach ($this->uploads as $upload) { 93 | $upload->forceDelete(); 94 | } 95 | 96 | if (Storage::exists('uploads/' . md5($this->id))) { 97 | Storage::deleteDirectory('uploads/' . md5($this->id)); 98 | } 99 | 100 | if (Storage::exists('thumbnails/' . md5($this->id))) { 101 | Storage::deleteDirectory('thumbnails/' . md5($this->id)); 102 | } 103 | 104 | $this->invalidateCache(); 105 | $this->invalidateGlobalCache(); 106 | 107 | return parent::forceDelete(); 108 | } 109 | 110 | public function invalidateCache() 111 | { 112 | Cache::forget('uploads_size:' . $this->id); 113 | Cache::forget('uploads_count:' . $this->id); 114 | } 115 | 116 | private function invalidateGlobalCache() { 117 | Cache::forget('users'); 118 | } 119 | 120 | public function setEnabledAttribute($value) 121 | { 122 | $this->attributes['enabled'] = $value; 123 | $this->invalidateGlobalCache(); 124 | } 125 | 126 | public function setConfirmedAttribute($value) 127 | { 128 | $this->attributes['confirmed'] = $value; 129 | $this->invalidateGlobalCache(); 130 | } 131 | 132 | public function getUploadsCount() 133 | { 134 | return Cache::rememberForever('uploads_count:' . $this->id, function () { 135 | return $this->uploads->count(); 136 | }); 137 | } 138 | 139 | public function getStorageQuota() 140 | { 141 | $userStorageQuota = Helpers::formatBytes($this->getUploadsSize()); 142 | if (config('upste.user_storage_quota') > 0 && !$this->isPrivilegedUser()) { 143 | $userStorageQuota = sprintf("%s / %s", $userStorageQuota, Helpers::formatBytes(config('upste.user_storage_quota'))); 144 | } 145 | 146 | return $userStorageQuota; 147 | } 148 | 149 | public function getUploadsSize() 150 | { 151 | return Cache::rememberForever('uploads_size:' . $this->id, function () { 152 | return $this->uploads->sum('size'); 153 | }); 154 | } 155 | 156 | public function isPrivilegedUser() 157 | { 158 | return $this->admin || $this->isSuperUser(); 159 | } 160 | 161 | public function isSuperUser() 162 | { 163 | return $this->id === 1; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/Models/UserPreferences.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Models\User'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'App\Listeners\EventListener', 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any other events for your application. 23 | * 24 | * @param \Illuminate\Contracts\Events\Dispatcher $events 25 | * @return void 26 | */ 27 | public function boot(DispatcherContract $events) 28 | { 29 | parent::boot($events); 30 | 31 | // 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | model('user', 'App\Models\User'); 31 | $router->model('upload', 'App\Models\Upload'); 32 | } 33 | 34 | /** 35 | * Define the routes for the application. 36 | * 37 | * @param \Illuminate\Routing\Router $router 38 | * @return void 39 | */ 40 | public function map(Router $router) 41 | { 42 | $router->group(['namespace' => $this->namespace], function ($router) { 43 | require app_path('Http/routes.php'); 44 | }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 32 | 33 | $status = $kernel->handle( 34 | $input = new Symfony\Component\Console\Input\ArgvInput, 35 | new Symfony\Component\Console\Output\ConsoleOutput 36 | ); 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Shutdown The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once Artisan has finished running. We will fire off the shutdown events 44 | | so that any final work may be done by the application before we shut 45 | | down the process. This is the last thing to happen to the request. 46 | | 47 | */ 48 | 49 | $kernel->terminate($input, $status); 50 | 51 | exit($status); 52 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/autoload.php: -------------------------------------------------------------------------------- 1 | =5.5.9", 23 | "laravel/framework": "5.2.*", 24 | "barryvdh/laravel-debugbar": "v2.2.*", 25 | "barryvdh/laravel-ide-helper": "v2.1.*", 26 | "doctrine/dbal": "2.3.*", 27 | "laracasts/flash": "1.3.*", 28 | "shrikeh/teapot": "1.0.*", 29 | "intervention/image": "^2.3", 30 | "greggilbert/recaptcha": "^2.1", 31 | "msurguy/honeypot": "^1.0", 32 | "predis/predis": "^1.1", 33 | "ralouphie/mimey": "^1.0" 34 | }, 35 | "require-dev": { 36 | "fzaninotto/faker": "~1.4", 37 | "mockery/mockery": "0.9.*", 38 | "phpunit/phpunit": "~4.0", 39 | "phpspec/phpspec": "~2.1", 40 | "symfony/css-selector": "~3.0", 41 | "symfony/dom-crawler": "~3.0" 42 | 43 | }, 44 | "autoload": { 45 | "classmap": [ 46 | "database" 47 | ], 48 | "files": [ 49 | "app/Http/helpers.php" 50 | ], 51 | "psr-4": { 52 | "App\\": "app/" 53 | } 54 | }, 55 | "autoload-dev": { 56 | "classmap": [ 57 | "tests/TestCase.php" 58 | ] 59 | }, 60 | "scripts": { 61 | "post-install-cmd": [ 62 | "php -r \"copy('.env.example', '.env');\"", 63 | "php artisan key:generate", 64 | "npm install", 65 | "gulp --production", 66 | "php artisan optimize" 67 | ], 68 | "pre-update-cmd": [ 69 | "php artisan down" 70 | ], 71 | "post-update-cmd": [ 72 | "Illuminate\\Foundation\\ComposerScripts::postUpdate", 73 | "php artisan ide-helper:generate", 74 | "php artisan ide-helper:meta", 75 | "npm update", 76 | "gulp --production", 77 | "php artisan migrate", 78 | "php artisan optimize", 79 | "php artisan cache:clear", 80 | "php artisan config:cache", 81 | "php artisan view:clear", 82 | "php artisan route:cache", 83 | "php artisan queue:restart", 84 | "php artisan up" 85 | ], 86 | "cs": "phpcs --standard=PSR2 app/ || true", 87 | "cbf": "phpcbf --standard=PSR2 app/ || true", 88 | "recache": [ 89 | "php artisan down", 90 | "php artisan optimize", 91 | "php artisan cache:clear", 92 | "php artisan config:cache", 93 | "php artisan view:clear", 94 | "php artisan route:cache", 95 | "php artisan up" 96 | ] 97 | }, 98 | "config": { 99 | "preferred-install": "dist" 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'web', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'api' => [ 45 | 'driver' => 'token', 46 | 'provider' => 'users', 47 | ], 48 | ], 49 | 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | User Providers 53 | |-------------------------------------------------------------------------- 54 | | 55 | | All authentication drivers have a user provider. This defines how the 56 | | users are actually retrieved out of your database or other storage 57 | | mechanisms used by this application to persist your user's data. 58 | | 59 | | If you have multiple user tables or models you may configure multiple 60 | | sources which represent each model / table. These sources may then 61 | | be assigned to any extra authentication guards you have defined. 62 | | 63 | | Supported: "database", "eloquent" 64 | | 65 | */ 66 | 67 | 'providers' => [ 68 | 'users' => [ 69 | 'driver' => 'eloquent', 70 | 'model' => App\Models\User::class, 71 | ], 72 | 73 | // 'users' => [ 74 | // 'driver' => 'database', 75 | // 'table' => 'users', 76 | // ], 77 | ], 78 | 79 | /* 80 | |-------------------------------------------------------------------------- 81 | | Resetting Passwords 82 | |-------------------------------------------------------------------------- 83 | | 84 | | Here you may set the options for resetting passwords including the view 85 | | that is your password reset e-mail. You may also set the name of the 86 | | table that maintains all of the reset tokens for your application. 87 | | 88 | | You may specify multiple password reset configurations if you have more 89 | | than one user table or model in the application and you want to have 90 | | separate password reset settings based on the specific user types. 91 | | 92 | | The expire time is the number of minutes that the reset token should be 93 | | considered valid. This security feature keeps tokens short-lived so 94 | | they have less time to be guessed. You may change this as needed. 95 | | 96 | */ 97 | 98 | 'passwords' => [ 99 | 'users' => [ 100 | 'provider' => 'users', 101 | 'email' => 'emails.user.password_reset', 102 | 'table' => 'password_resets', 103 | 'expire' => 60, 104 | ], 105 | ], 106 | 107 | ]; 108 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'pusher'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Broadcast Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may define all of the broadcast connections that will be used 24 | | to broadcast events to other systems or over websockets. Samples of 25 | | each available type of connection are provided inside this array. 26 | | 27 | */ 28 | 29 | 'connections' => [ 30 | 31 | 'pusher' => [ 32 | 'driver' => 'pusher', 33 | 'key' => env('PUSHER_KEY'), 34 | 'secret' => env('PUSHER_SECRET'), 35 | 'app_id' => env('PUSHER_APP_ID'), 36 | ], 37 | 38 | 'redis' => [ 39 | 'driver' => 'redis', 40 | 'connection' => 'default', 41 | ], 42 | 43 | 'log' => [ 44 | 'driver' => 'log', 45 | ], 46 | 47 | ], 48 | 49 | ]; 50 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Cache Stores 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may define all of the cache "stores" for your application as 24 | | well as their drivers. You may even define multiple stores for the 25 | | same cache driver to group types of items stored in your caches. 26 | | 27 | */ 28 | 29 | 'stores' => [ 30 | 31 | 'apc' => [ 32 | 'driver' => 'apc', 33 | ], 34 | 35 | 'array' => [ 36 | 'driver' => 'array', 37 | ], 38 | 39 | 'database' => [ 40 | 'driver' => 'database', 41 | 'table' => 'cache', 42 | 'connection' => null, 43 | ], 44 | 45 | 'file' => [ 46 | 'driver' => 'file', 47 | 'path' => storage_path('framework/cache'), 48 | ], 49 | 50 | 'memcached' => [ 51 | 'driver' => 'memcached', 52 | 'servers' => [ 53 | [ 54 | 'host' => '127.0.0.1', 'port' => 11211, 'weight' => 100, 55 | ], 56 | ], 57 | ], 58 | 59 | 'redis' => [ 60 | 'driver' => 'redis', 61 | 'connection' => 'default', 62 | ], 63 | 64 | ], 65 | 66 | /* 67 | |-------------------------------------------------------------------------- 68 | | Cache Key Prefix 69 | |-------------------------------------------------------------------------- 70 | | 71 | | When utilizing a RAM based store such as APC or Memcached, there might 72 | | be other applications utilizing the same cache. So, we'll specify a 73 | | value to get prefixed to all our keys so we can avoid collisions. 74 | | 75 | */ 76 | 77 | 'prefix' => 'pste', 78 | 79 | ]; 80 | -------------------------------------------------------------------------------- /config/compile.php: -------------------------------------------------------------------------------- 1 | [ 17 | ], 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Compiled File Providers 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Here you may list service providers which define a "compiles" function 25 | | that returns additional files that should be compiled, providing an 26 | | easy way to get common files from any packages you are utilizing. 27 | | 28 | */ 29 | 30 | 'providers' => [ 31 | // 32 | ], 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | PDO::FETCH_CLASS, 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Database Connection Name 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may specify which of the database connections below you wish 24 | | to use as your default connection for all database work. Of course 25 | | you may use many connections at once using the Database library. 26 | | 27 | */ 28 | 29 | 'default' => env('DB_CONNECTION', 'mysql'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Database Connections 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here are each of the database connections setup for your application. 37 | | Of course, examples of configuring each database platform that is 38 | | supported by Laravel is shown below to make development simple. 39 | | 40 | | 41 | | All database work in Laravel is done through the PHP PDO facilities 42 | | so make sure you have the driver for your particular database of 43 | | choice installed on your machine before you begin development. 44 | | 45 | */ 46 | 47 | 'connections' => [ 48 | 49 | 'sqlite' => [ 50 | 'driver' => 'sqlite', 51 | 'database' => storage_path('database.sqlite'), 52 | 'prefix' => '', 53 | ], 54 | 55 | 'mysql' => [ 56 | 'driver' => 'mysql', 57 | 'host' => env('DB_HOST', 'localhost'), 58 | 'database' => env('DB_DATABASE', 'forge'), 59 | 'username' => env('DB_USERNAME', 'forge'), 60 | 'password' => env('DB_PASSWORD', ''), 61 | 'charset' => 'utf8', 62 | 'collation' => 'utf8_bin', 63 | 'prefix' => '', 64 | 'strict' => false, 65 | ], 66 | 67 | 'pgsql' => [ 68 | 'driver' => 'pgsql', 69 | 'host' => env('DB_HOST', 'localhost'), 70 | 'database' => env('DB_DATABASE', 'forge'), 71 | 'username' => env('DB_USERNAME', 'forge'), 72 | 'password' => env('DB_PASSWORD', ''), 73 | 'charset' => 'utf8', 74 | 'prefix' => '', 75 | 'schema' => 'public', 76 | ], 77 | 78 | 'sqlsrv' => [ 79 | 'driver' => 'sqlsrv', 80 | 'host' => env('DB_HOST', 'localhost'), 81 | 'database' => env('DB_DATABASE', 'forge'), 82 | 'username' => env('DB_USERNAME', 'forge'), 83 | 'password' => env('DB_PASSWORD', ''), 84 | 'charset' => 'utf8', 85 | 'prefix' => '', 86 | ], 87 | 88 | ], 89 | 90 | /* 91 | |-------------------------------------------------------------------------- 92 | | Migration Repository Table 93 | |-------------------------------------------------------------------------- 94 | | 95 | | This table keeps track of all the migrations that have already run for 96 | | your application. Using this information, we can determine which of 97 | | the migrations on disk haven't actually been run in the database. 98 | | 99 | */ 100 | 101 | 'migrations' => 'migrations', 102 | 103 | /* 104 | |-------------------------------------------------------------------------- 105 | | Redis Databases 106 | |-------------------------------------------------------------------------- 107 | | 108 | | Redis is an open source, fast, and advanced key-value store that also 109 | | provides a richer set of commands than a typical key-value systems 110 | | such as APC or Memcached. Laravel makes it easy to dig right in. 111 | | 112 | */ 113 | 114 | 'redis' => [ 115 | 116 | 'cluster' => false, 117 | 118 | 'default' => [ 119 | 'host' => env('REDIS_HOST', '127.0.0.1'), 120 | 'path' => env('REDIS_PATH', '/tmp/redis.sock'), 121 | 'port' => env('REDIS_PORT', 6379), 122 | 'database' => env('REDIS_DATABASE', 0), 123 | 'scheme' => env('REDIS_SCHEME', 'tcp'), 124 | 'password' => env('REDIS_PASSWORD', null), 125 | ], 126 | 127 | ], 128 | 129 | ]; 130 | -------------------------------------------------------------------------------- /config/debugbar.php: -------------------------------------------------------------------------------- 1 | null, 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Storage settings 20 | |-------------------------------------------------------------------------- 21 | | 22 | | DebugBar stores data for session/ajax requests. 23 | | You can disable this, so the debugbar stores data in headers/session, 24 | | but this can cause problems with large data collectors. 25 | | By default, file storage (in the storage folder) is used. Redis and PDO 26 | | can also be used. For PDO, run the package migrations first. 27 | | 28 | */ 29 | 'storage' => [ 30 | 'enabled' => true, 31 | 'driver' => 'file', // redis, file, pdo 32 | 'path' => storage_path() . '/debugbar', // For file driver 33 | 'connection' => null, // Leave null for default connection (Redis/PDO) 34 | ], 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Vendors 39 | |-------------------------------------------------------------------------- 40 | | 41 | | Vendor files are included by default, but can be set to false. 42 | | This can also be set to 'js' or 'css', to only include javascript or css vendor files. 43 | | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) 44 | | and for js: jquery and and highlight.js 45 | | So if you want syntax highlighting, set it to true. 46 | | jQuery is set to not conflict with existing jQuery scripts. 47 | | 48 | */ 49 | 50 | 'include_vendors' => true, 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Capture Ajax Requests 55 | |-------------------------------------------------------------------------- 56 | | 57 | | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors), 58 | | you can use this option to disable sending the data through the headers. 59 | | 60 | */ 61 | 62 | 'capture_ajax' => true, 63 | 64 | /* 65 | |-------------------------------------------------------------------------- 66 | | Clockwork integration 67 | |-------------------------------------------------------------------------- 68 | | 69 | | The Debugbar can emulate the Clockwork headers, so you can use the Chrome 70 | | Extension, without the server-side code. It uses Debugbar collectors instead. 71 | | 72 | */ 73 | 'clockwork' => false, 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | DataCollectors 78 | |-------------------------------------------------------------------------- 79 | | 80 | | Enable/disable DataCollectors 81 | | 82 | */ 83 | 84 | 'collectors' => [ 85 | 'phpinfo' => true, // Php version 86 | 'messages' => true, // Messages 87 | 'time' => true, // Time Datalogger 88 | 'memory' => true, // Memory usage 89 | 'exceptions' => true, // Exception displayer 90 | 'log' => true, // Logs from Monolog (merged in messages if enabled) 91 | 'db' => true, // Show database (PDO) queries and bindings 92 | 'views' => true, // Views with their data 93 | 'route' => true, // Current route information 94 | 'laravel' => false, // Laravel version and environment 95 | 'events' => false, // All events fired 96 | 'default_request' => false, // Regular or special Symfony request logger 97 | 'symfony_request' => true, // Only one can be enabled.. 98 | 'mail' => true, // Catch mail messages 99 | 'logs' => false, // Add the latest log messages 100 | 'files' => false, // Show the included files 101 | 'config' => false, // Display config settings 102 | 'auth' => false, // Display Laravel authentication status 103 | 'session' => true, // Display session data 104 | ], 105 | 106 | /* 107 | |-------------------------------------------------------------------------- 108 | | Extra options 109 | |-------------------------------------------------------------------------- 110 | | 111 | | Configure some DataCollectors 112 | | 113 | */ 114 | 115 | 'options' => [ 116 | 'auth' => [ 117 | 'show_name' => false, // Also show the users name/email in the debugbar 118 | ], 119 | 'db' => [ 120 | 'with_params' => true, // Render SQL with the parameters substituted 121 | 'timeline' => false, // Add the queries to the timeline 122 | 'backtrace' => false, // EXPERIMENTAL: Use a backtrace to find the origin of the query in your files. 123 | 'explain' => [ // EXPERIMENTAL: Show EXPLAIN output on queries 124 | 'enabled' => false, 125 | 'types' => ['SELECT'], // array('SELECT', 'INSERT', 'UPDATE', 'DELETE'); for MySQL 5.6.3+ 126 | ], 127 | 'hints' => true, // Show hints for common mistakes 128 | ], 129 | 'mail' => [ 130 | 'full_log' => false 131 | ], 132 | 'views' => [ 133 | 'data' => false, //Note: Can slow down the application, because the data can be quite large.. 134 | ], 135 | 'route' => [ 136 | 'label' => true // show complete route on bar 137 | ], 138 | 'logs' => [ 139 | 'file' => null 140 | ], 141 | ], 142 | 143 | /* 144 | |-------------------------------------------------------------------------- 145 | | Inject Debugbar in Response 146 | |-------------------------------------------------------------------------- 147 | | 148 | | Usually, the debugbar is added just before
, by listening to the 149 | | Response after the App is done. If you disable this, you have to add them 150 | | in your template yourself. See http://phpdebugbar.com/docs/rendering.html 151 | | 152 | */ 153 | 154 | 'inject' => true, 155 | 156 | /* 157 | |-------------------------------------------------------------------------- 158 | | DebugBar route prefix 159 | |-------------------------------------------------------------------------- 160 | | 161 | | Sometimes you want to set route prefix to be used by DebugBar to load 162 | | its resources from. Usually the need comes from misconfigured web server or 163 | | from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 164 | | 165 | */ 166 | 'route_prefix' => '_debugbar', 167 | 168 | ]; 169 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | 'local', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Default Cloud Filesystem Disk 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Many applications store files both locally and in the cloud. For this 26 | | reason, you may specify a default "cloud" driver here. This driver 27 | | will be bound as the Cloud disk implementation in the container. 28 | | 29 | */ 30 | 31 | 'cloud' => 's3', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Filesystem Disks 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Here you may configure as many filesystem "disks" as you wish, and you 39 | | may even configure multiple disks of the same driver. Defaults have 40 | | been setup for each driver as an example of the required options. 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'ftp' => [ 52 | 'driver' => 'ftp', 53 | 'host' => 'ftp.example.com', 54 | 'username' => 'your-username', 55 | 'password' => 'your-password', 56 | 57 | // Optional FTP Settings... 58 | // 'port' => 21, 59 | // 'root' => '', 60 | // 'passive' => true, 61 | // 'ssl' => true, 62 | // 'timeout' => 30, 63 | ], 64 | 65 | 's3' => [ 66 | 'driver' => 's3', 67 | 'key' => 'your-key', 68 | 'secret' => 'your-secret', 69 | 'region' => 'your-region', 70 | 'bucket' => 'your-bucket', 71 | ], 72 | 73 | 'rackspace' => [ 74 | 'driver' => 'rackspace', 75 | 'username' => 'your-username', 76 | 'key' => 'your-key', 77 | 'container' => 'your-container', 78 | 'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/', 79 | 'region' => 'IAD', 80 | 'url_type' => 'publicURL', 81 | ], 82 | 83 | ], 84 | 85 | ]; 86 | -------------------------------------------------------------------------------- /config/ide-helper.php: -------------------------------------------------------------------------------- 1 | '_ide_helper', 15 | 'format' => 'php', 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Helper files to include 20 | |-------------------------------------------------------------------------- 21 | | 22 | | Include helper files. By default not included, but can be toggled with the 23 | | -- helpers (-H) option. Extra helper files can be included. 24 | | 25 | */ 26 | 27 | 'include_helpers' => false, 28 | 29 | 'helper_files' => [ 30 | base_path() . '/vendor/laravel/framework/src/Illuminate/Support/helpers.php', 31 | ], 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Model locations to include 36 | |-------------------------------------------------------------------------- 37 | | 38 | | Define in which directories the ide-helper:models command should look 39 | | for models. 40 | | 41 | */ 42 | 43 | 'model_locations' => [ 44 | 'app', 45 | ], 46 | 47 | 48 | /* 49 | |-------------------------------------------------------------------------- 50 | | Extra classes 51 | |-------------------------------------------------------------------------- 52 | | 53 | | These implementations are not really extended, but called with magic functions 54 | | 55 | */ 56 | 57 | 'extra' => [ 58 | 'Eloquent' => ['Illuminate\Database\Eloquent\Builder', 'Illuminate\Database\Query\Builder'], 59 | 'Session' => ['Illuminate\Session\Store'], 60 | ], 61 | 62 | 'magic' => [ 63 | 'Log' => [ 64 | 'debug' => 'Monolog\Logger::addDebug', 65 | 'info' => 'Monolog\Logger::addInfo', 66 | 'notice' => 'Monolog\Logger::addNotice', 67 | 'warning' => 'Monolog\Logger::addWarning', 68 | 'error' => 'Monolog\Logger::addError', 69 | 'critical' => 'Monolog\Logger::addCritical', 70 | 'alert' => 'Monolog\Logger::addAlert', 71 | 'emergency' => 'Monolog\Logger::addEmergency', 72 | ] 73 | ], 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Interface implementations 78 | |-------------------------------------------------------------------------- 79 | | 80 | | These interfaces will be replaced with the implementing class. Some interfaces 81 | | are detected by the helpers, others can be listed below. 82 | | 83 | */ 84 | 85 | 'interfaces' => [ 86 | '\Illuminate\Contracts\Auth\Authenticatable' => config('auth.model', 'App\User'), 87 | ], 88 | 89 | /* 90 | |-------------------------------------------------------------------------- 91 | | Support for custom DB types 92 | |-------------------------------------------------------------------------- 93 | | 94 | | This setting allow you to map any custom database type (that you may have 95 | | created using CREATE TYPE statement or imported using database plugin 96 | | / extension to a Doctrine type. 97 | | 98 | | Each key in this array is a name of the Doctrine2 DBAL Platform. Currently valid names are: 99 | | 'postgresql', 'db2', 'drizzle', 'mysql', 'oracle', 'sqlanywhere', 'sqlite', 'mssql' 100 | | 101 | | This name is returned by getName() method of the specific Doctrine/DBAL/Platforms/AbstractPlatform descendant 102 | | 103 | | The value of the array is an array of type mappings. Key is the name of the custom type, 104 | | (for example, "jsonb" from Postgres 9.4) and the value is the name of the corresponding Doctrine2 type (in 105 | | our case it is 'json_array'. Doctrine types are listed here: 106 | | http://doctrine-dbal.readthedocs.org/en/latest/reference/types.html 107 | | 108 | | So to support jsonb in your models when working with Postgres, just add the following entry to the array below: 109 | | 110 | | "postgresql" => array( 111 | | "jsonb" => "json_array", 112 | | ), 113 | | 114 | */ 115 | 'custom_db_types' => [ 116 | 117 | ], 118 | 119 | ]; 120 | -------------------------------------------------------------------------------- /config/image.php: -------------------------------------------------------------------------------- 1 | 'gd' 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_DRIVER', 'smtp'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | SMTP Host Address 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may provide the host address of the SMTP server used by your 26 | | applications. A default option is provided that is compatible with 27 | | the Mailgun mail service which will provide reliable deliveries. 28 | | 29 | */ 30 | 31 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | SMTP Host Port 36 | |-------------------------------------------------------------------------- 37 | | 38 | | This is the SMTP port used by your application to deliver e-mails to 39 | | users of the application. Like the host we have set this value to 40 | | stay compatible with the Mailgun e-mail application by default. 41 | | 42 | */ 43 | 44 | 'port' => env('MAIL_PORT', 587), 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Global "From" Address 49 | |-------------------------------------------------------------------------- 50 | | 51 | | You may wish for all e-mails sent by your application to be sent from 52 | | the same address. Here, you may specify a name and address that is 53 | | used globally for all e-mails that are sent by your application. 54 | | 55 | */ 56 | 57 | 'from' => ['address' => env('MAIL_FROM', null), 'name' => env('SITE_NAME', null)], 58 | 59 | /* 60 | |-------------------------------------------------------------------------- 61 | | E-Mail Encryption Protocol 62 | |-------------------------------------------------------------------------- 63 | | 64 | | Here you may specify the encryption protocol that should be used when 65 | | the application send e-mail messages. A sensible default using the 66 | | transport layer security protocol should provide great security. 67 | | 68 | */ 69 | 70 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 71 | 72 | /* 73 | |-------------------------------------------------------------------------- 74 | | SMTP Server Username 75 | |-------------------------------------------------------------------------- 76 | | 77 | | If your SMTP server requires a username for authentication, you should 78 | | set it here. This will get used to authenticate with your server on 79 | | connection. You may also set the "password" value below this one. 80 | | 81 | */ 82 | 83 | 'username' => env('MAIL_USERNAME'), 84 | 85 | /* 86 | |-------------------------------------------------------------------------- 87 | | SMTP Server Password 88 | |-------------------------------------------------------------------------- 89 | | 90 | | Here you may set the password required by your SMTP server to send out 91 | | messages from your application. This will be given to the server on 92 | | connection so that the application will be able to send messages. 93 | | 94 | */ 95 | 96 | 'password' => env('MAIL_PASSWORD'), 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Sendmail System Path 101 | |-------------------------------------------------------------------------- 102 | | 103 | | When using the "sendmail" driver to send e-mails, we will need to know 104 | | the path to where Sendmail lives on this server. A default path has 105 | | been provided here, which will work well on most of your systems. 106 | | 107 | */ 108 | 109 | 'sendmail' => '/usr/sbin/sendmail -bs', 110 | 111 | /* 112 | |-------------------------------------------------------------------------- 113 | | Mail "Pretend" 114 | |-------------------------------------------------------------------------- 115 | | 116 | | When this option is enabled, e-mail will not actually be sent over the 117 | | web and will instead be written to your application's logs files so 118 | | you may inspect the message. This is great for local development. 119 | | 120 | */ 121 | 122 | 'pretend' => false, 123 | 124 | ]; 125 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Queue Connections 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may configure the connection information for each server that 27 | | is used by your application. A default configuration has been added 28 | | for each back-end shipped with Laravel. You are free to add more. 29 | | 30 | */ 31 | 32 | 'connections' => [ 33 | 34 | 'sync' => [ 35 | 'driver' => 'sync', 36 | ], 37 | 38 | 'database' => [ 39 | 'driver' => 'database', 40 | 'table' => 'jobs', 41 | 'queue' => 'default', 42 | 'expire' => 60, 43 | ], 44 | 45 | 'beanstalkd' => [ 46 | 'driver' => 'beanstalkd', 47 | 'host' => 'localhost', 48 | 'queue' => 'default', 49 | 'ttr' => 60, 50 | ], 51 | 52 | 'sqs' => [ 53 | 'driver' => 'sqs', 54 | 'key' => 'your-public-key', 55 | 'secret' => 'your-secret-key', 56 | 'queue' => 'your-queue-url', 57 | 'region' => 'us-east-1', 58 | ], 59 | 60 | 'iron' => [ 61 | 'driver' => 'iron', 62 | 'host' => 'mq-aws-us-east-1.iron.io', 63 | 'token' => 'your-token', 64 | 'project' => 'your-project-id', 65 | 'queue' => 'your-queue-name', 66 | 'encrypt' => true, 67 | ], 68 | 69 | 'redis' => [ 70 | 'driver' => 'redis', 71 | 'connection' => 'default', 72 | 'queue' => 'default', 73 | 'expire' => 60, 74 | ], 75 | 76 | ], 77 | 78 | /* 79 | |-------------------------------------------------------------------------- 80 | | Failed Queue Jobs 81 | |-------------------------------------------------------------------------- 82 | | 83 | | These options configure the behavior of failed queue job logging so you 84 | | can control which database and table are used to store the jobs that 85 | | have failed. You may change them to any database / table you wish. 86 | | 87 | */ 88 | 89 | 'failed' => [ 90 | 'database' => 'mysql', 'table' => 'failed_jobs', 91 | ], 92 | 93 | ]; 94 | -------------------------------------------------------------------------------- /config/recaptcha.php: -------------------------------------------------------------------------------- 1 | env('RECAPTCHA_PUBLIC_KEY', ''), 17 | 'private_key' => env('RECAPTCHA_PRIVATE_KEY', ''), 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Template 22 | |-------------------------------------------------------------------------- 23 | | 24 | | Set a template to use if you don't want to use the standard one. 25 | | 26 | */ 27 | 'template' => '', 28 | 29 | /* 30 | |-------------------------------------------------------------------------- 31 | | Driver 32 | |-------------------------------------------------------------------------- 33 | | 34 | | Determine how to call out to get response; values are 'curl' or 'native'. 35 | | Only applies to v2. 36 | | 37 | */ 38 | 'driver' => 'curl', 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Options 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Various options for the driver 46 | | 47 | */ 48 | 'options' => [ 49 | 50 | 'curl_timeout' => 1, 51 | 52 | ], 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | Version 57 | |-------------------------------------------------------------------------- 58 | | 59 | | Set which version of ReCaptcha to use. 60 | | 61 | */ 62 | 63 | 'version' => 2, 64 | 65 | ]; 66 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => '', 19 | 'secret' => '', 20 | ], 21 | 22 | 'mandrill' => [ 23 | 'secret' => '', 24 | ], 25 | 26 | 'ses' => [ 27 | 'key' => '', 28 | 'secret' => '', 29 | 'region' => 'us-east-1', 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => App\Models\User::class, 34 | 'key' => '', 35 | 'secret' => '', 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /config/session.php: -------------------------------------------------------------------------------- 1 | env('SESSION_DRIVER', 'database'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Session Lifetime 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may specify the number of minutes that you wish the session 27 | | to be allowed to remain idle before it expires. If you want them 28 | | to immediately expire on the browser closing, set that option. 29 | | 30 | */ 31 | 32 | 'lifetime' => 120, 33 | 34 | 'expire_on_close' => false, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Session Encryption 39 | |-------------------------------------------------------------------------- 40 | | 41 | | This option allows you to easily specify that all of your session data 42 | | should be encrypted before it is stored. All encryption will be run 43 | | automatically by Laravel and you can use the Session like normal. 44 | | 45 | */ 46 | 47 | 'encrypt' => true, 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Session File Location 52 | |-------------------------------------------------------------------------- 53 | | 54 | | When using the native session driver, we need a location where session 55 | | files may be stored. A default has been set for you but a different 56 | | location may be specified. This is only needed for file sessions. 57 | | 58 | */ 59 | 60 | 'files' => storage_path('framework/sessions'), 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Session Database Connection 65 | |-------------------------------------------------------------------------- 66 | | 67 | | When using the "database" or "redis" session drivers, you may specify a 68 | | connection that should be used to manage these sessions. This should 69 | | correspond to a connection in your database configuration options. 70 | | 71 | */ 72 | 73 | 'connection' => null, 74 | 75 | /* 76 | |-------------------------------------------------------------------------- 77 | | Session Database Table 78 | |-------------------------------------------------------------------------- 79 | | 80 | | When using the "database" session driver, you may specify the table we 81 | | should use to manage the sessions. Of course, a sensible default is 82 | | provided for you; however, you are free to change this as needed. 83 | | 84 | */ 85 | 86 | 'table' => 'sessions', 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Session Sweeping Lottery 91 | |-------------------------------------------------------------------------- 92 | | 93 | | Some session drivers must manually sweep their storage location to get 94 | | rid of old sessions from storage. Here are the chances that it will 95 | | happen on a given request. By default, the odds are 2 out of 100. 96 | | 97 | */ 98 | 99 | 'lottery' => [2, 100], 100 | 101 | /* 102 | |-------------------------------------------------------------------------- 103 | | Session Cookie Name 104 | |-------------------------------------------------------------------------- 105 | | 106 | | Here you may change the name of the cookie used to identify a session 107 | | instance by ID. The name specified here will get used every time a 108 | | new session cookie is created by the framework for every driver. 109 | | 110 | */ 111 | 112 | 'cookie' => 'upste_session', 113 | 114 | /* 115 | |-------------------------------------------------------------------------- 116 | | Session Cookie Path 117 | |-------------------------------------------------------------------------- 118 | | 119 | | The session cookie path determines the path for which the cookie will 120 | | be regarded as available. Typically, this will be the root path of 121 | | your application but you are free to change this when necessary. 122 | | 123 | */ 124 | 125 | 'path' => '/', 126 | 127 | /* 128 | |-------------------------------------------------------------------------- 129 | | Session Cookie Domain 130 | |-------------------------------------------------------------------------- 131 | | 132 | | Here you may change the domain of the cookie used to identify a session 133 | | in your application. This will determine which domains the cookie is 134 | | available to in your application. A sensible default has been set. 135 | | 136 | */ 137 | 138 | 'domain' => env('COOKIE_DOMAIN', null), 139 | 140 | /* 141 | |-------------------------------------------------------------------------- 142 | | HTTPS Only Cookies 143 | |-------------------------------------------------------------------------- 144 | | 145 | | By setting this option to true, session cookies will only be sent back 146 | | to the server if the browser has a HTTPS connection. This will keep 147 | | the cookie from being sent to you if it can not be done securely. 148 | | 149 | */ 150 | 151 | 'secure' => env('SECURE_COOKIES', true), 152 | 153 | ]; 154 | -------------------------------------------------------------------------------- /config/upste.php: -------------------------------------------------------------------------------- 1 | env('SITE_NAME', 'uPste'), 5 | 'upload_url' => str_finish(env('UPLOAD_URL', 'https://a.example.com'), '/'), 6 | 'upload_domain' => parse_url(env('UPLOAD_URL', 'https://a.example.com'), PHP_URL_HOST), 7 | 'upload_slug_length' => env('UPLOAD_SLUG_LENGTH', 3), 8 | 'owner_name' => env('OWNER_NAME', 'Me'), 9 | 'owner_email' => env('OWNER_EMAIL', 'me@example.com'), 10 | 'owner_gpg' => env('OWNER_GPG', null), 11 | 'upload_limit' => env('PER_UPLOAD_LIMIT', 20) * 1000000, 12 | 'irc_channel' => env('IRC_CHANNEL', null), 13 | 'irc_server' => env('IRC_SERVER', null), 14 | 'strip_exif' => env('STRIP_EXIF', true), 15 | 'user_storage_quota' => env('USER_STORAGE_QUOTA', 0) * 1000000, // Megabytes to bytes 16 | 'recaptcha_enabled' => env('RECAPTCHA_PUBLIC_KEY', null) && env('RECAPTCHA_PUBLIC_KEY', null), 17 | 'sendfile_method' => env('SENDFILE_METHOD', null), 18 | 'require_user_approval' => env('REQUIRE_USER_APPROVAL', true), 19 | 'password_hash_rounds' => env('PASSWORD_HASH_ROUNDS', 10), 20 | 'require_email_verification' => env('REQUIRE_EMAIL_VERIFICATION', false), 21 | ]; 22 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | realpath(base_path('resources/views')), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => realpath(storage_path('framework/views')), 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function ($faker) { 15 | return [ 16 | 'name' => $faker->name, 17 | 'email' => $faker->email, 18 | 'password' => str_random(10), 19 | 'remember_token' => str_random(10), 20 | ]; 21 | }); 22 | -------------------------------------------------------------------------------- /database/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheReverend403/uPste/78de9db3c3b47423ee6ac1212560ed5f14011e6f/database/migrations/.gitkeep -------------------------------------------------------------------------------- /database/migrations/2015_12_09_172957_create_users_table.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 17 | $table->increments('id'); 18 | $table->string('name')->unique(); 19 | $table->string('email')->unique(); 20 | $table->string('apikey', 64)->unique(); 21 | $table->string('password', 60); 22 | $table->boolean('admin')->default(false); 23 | $table->boolean('enabled')->default(false); 24 | $table->boolean('banned')->default(false); 25 | $table->rememberToken(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::drop('users'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2015_12_09_173005_create_uploads_table.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 17 | $table->increments('id'); 18 | $table->integer('user_id')->unsigned(); 19 | $table->string('hash', 20); 20 | $table->string('name', 32)->unique(); 21 | $table->unsignedBigInteger('size'); 22 | $table->string('original_name'); 23 | $table->timestamps(); 24 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('uploads'); 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /database/migrations/2015_12_09_173022_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 17 | $table->string('email')->index(); 18 | $table->string('token')->index(); 19 | $table->timestamp('created_at'); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2015_12_09_235053_fix_hash_length_for_uploads_table.php: -------------------------------------------------------------------------------- 1 | string('hash', 40)->change(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('uploads', function (Blueprint $table) { 28 | $table->string('hash', 20)->change(); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2015_12_11_221322_create_sessions_table.php: -------------------------------------------------------------------------------- 1 | string('id')->unique(); 17 | $table->text('payload'); 18 | $table->integer('last_activity'); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::drop('sessions'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2015_12_15_101257_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 17 | $table->string('queue'); 18 | $table->longText('payload'); 19 | $table->tinyInteger('attempts')->unsigned(); 20 | $table->tinyInteger('reserved')->unsigned(); 21 | $table->unsignedInteger('reserved_at')->nullable(); 22 | $table->unsignedInteger('available_at'); 23 | $table->unsignedInteger('created_at'); 24 | $table->index(['queue', 'reserved', 'reserved_at']); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down() 34 | { 35 | Schema::drop('jobs'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2015_12_15_101552_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->timestamp('failed_at'); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::drop('failed_jobs'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2016_02_20_173427_update_session_drivers.php: -------------------------------------------------------------------------------- 1 | integer('user_id')->nullable(); 17 | $table->string('ip_address')->nullable(); 18 | $table->text('user_agent'); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::table('sessions', function (Blueprint $table) { 30 | $table->dropColumn(['user_id', 'ip_address', 'user_agent']); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2016_03_20_131515_add_original_hash.php: -------------------------------------------------------------------------------- 1 | string('original_hash', 40); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('uploads', function (Blueprint $table) { 28 | $table->dropColumn('original_hash'); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_03_30_213843_increase_password_length.php: -------------------------------------------------------------------------------- 1 | string('password', 255)->change(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('users', function (Blueprint $table) { 28 | $table->string('password', 60)->change(); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_04_01_022122_add_view_counter.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('downloads')->default(0); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('uploads', function (Blueprint $table) { 28 | $table->dropColumn('downloads'); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_04_01_025729_rename_download.php: -------------------------------------------------------------------------------- 1 | renameColumn('downloads', 'views'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('uploads', function (Blueprint $table) { 28 | $table->renameColumn('views', 'downloads'); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_04_03_195138_create_preferences_table.php: -------------------------------------------------------------------------------- 1 | engine = 'InnoDB'; 17 | $table->integer('user_id')->unsigned(); 18 | $table->string('timezone')->default('UTC'); 19 | $table->integer('pagination_items')->default(9); 20 | $table->timestamps(); 21 | $table->foreign('user_id')->references('id')->on('users')->onDelete('CASCADE'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::drop('preferences'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2016_04_03_213046_update_default_pagination.php: -------------------------------------------------------------------------------- 1 | integer('pagination_items')->default(6)->change(); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | * 23 | * @return void 24 | */ 25 | public function down() 26 | { 27 | Schema::table('preferences', function (Blueprint $table) { 28 | $table->integer('pagination_items')->default(9)->change(); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2016_07_26_153539_add_email_confirmation.php: -------------------------------------------------------------------------------- 1 | boolean('confirmed')->default(false); 17 | $table->string('confirmation_code', 32)->nullable(); 18 | }); 19 | // For existing users. 20 | DB::table('users')->update(['confirmed' => true]); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::table('users', function (Blueprint $table) { 31 | $table->dropColumn('confirmation_code', 'confirmed'); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/seeds/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheReverend403/uPste/78de9db3c3b47423ee6ac1212560ed5f14011e6f/database/seeds/.gitkeep -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UserTableSeeder::class); 18 | 19 | Model::reguard(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Elixir Asset Management 4 | |-------------------------------------------------------------------------- 5 | | 6 | | Elixir provides a clean, fluent API for defining some basic Gulp tasks 7 | | for your Laravel application. By default, we are compiling the Sass 8 | | file for our application, as well as publishing vendor resources. 9 | | 10 | */ 11 | 12 | // Disable notify-send 13 | process.env.DISABLE_NOTIFIER = true; 14 | 15 | var elixir = require('laravel-elixir'); 16 | elixir.config.sourcemaps = false; 17 | 18 | elixir(function(mix) { 19 | mix.sass([ 20 | 'global.scss' 21 | ], 'public/assets/css/global.css'); 22 | 23 | mix.sass([ 24 | 'error.scss' 25 | ], 'public/assets/css/error.css'); 26 | 27 | mix.sass('thumbnailhover.scss', 'public/assets/css/thumbnailhover.css'); 28 | 29 | mix.styles([ 30 | '../../../node_modules/dropzone/dist/basic.css' 31 | ], 'public/assets/css/dropzone.css'); 32 | 33 | mix.scripts('thumbnailhover.js', 'public/assets/js/thumbnailhover.js'); 34 | 35 | mix.scripts([ 36 | '../../../node_modules/dropzone/dist/dropzone.js', 37 | 'dropzone.js' 38 | ], 'public/assets/js/dropzone.js'); 39 | 40 | mix.scripts([ 41 | '../../../node_modules/jquery/dist/jquery.js', 42 | '../../../node_modules/bootstrap-sass/assets/javascripts/bootstrap/modal.js', 43 | '../../../node_modules/bootstrap-sass/assets/javascripts/bootstrap/transition.js', 44 | '../../../node_modules/bootstrap-sass/assets/javascripts/bootstrap/collapse.js', 45 | '../../../node_modules/bootstrap-sass/assets/javascripts/bootstrap/dropdown.js', 46 | '../../../node_modules/bootstrap-sass/assets/javascripts/bootstrap/alert.js', 47 | 'global.js' 48 | ], 'public/assets/js/global.js'); 49 | 50 | mix.version([ 51 | 'assets/css/global.css', 52 | 'assets/css/error.css', 53 | 'assets/js/thumbnailhover.js', 54 | 'assets/css/thumbnailhover.css', 55 | 'assets/css/dropzone.css', 56 | 'assets/js/global.js', 57 | 'assets/js/dropzone.js', 58 | 'assets/img/favicon.png', 59 | 'assets/img/thumbnail.png' 60 | ]); 61 | }); 62 | -------------------------------------------------------------------------------- /nginx.conf.example: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name u.localhost; 4 | root /path/to/pste/public; 5 | index index.php; 6 | 7 | client_max_body_size 100M; 8 | 9 | location ~ /index\.php$ { 10 | include fastcgi_params; 11 | try_files $uri =404; 12 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 13 | fastcgi_pass unix:/var/run/php7-fpm.sock; 14 | fastcgi_index index.php; 15 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 16 | } 17 | 18 | location / { 19 | try_files $uri $uri/ /index.php?$query_string; 20 | } 21 | 22 | location ~ /robots.txt { 23 | allow all; 24 | log_not_found off; 25 | access_log off; 26 | } 27 | } 28 | 29 | server { 30 | listen 80; 31 | server_name a.localhost; 32 | root /path/to/pste/public; 33 | index index.php; 34 | 35 | include mime.types; 36 | 37 | # Very important to at least serve .php files as plaintext, otherwise your site WILL be hacked in no time at all. 38 | # SVG served as plaintext due to https://hackerone.com/reports/148853 39 | types { 40 | text/plain txt ini html htm shtml sh desktop; 41 | text/plain pl go py cs c java fish php svg js; 42 | text/plain rb rs lua ls hy asm S conf vim; 43 | text/plain moon log tcl tk md coffee; 44 | text/plain scss ts less d hs; 45 | } 46 | 47 | gzip on; 48 | expires modified +1h; 49 | 50 | # It is very important to be specific here, otherwise your users won't be able to upload PHP files to be served as plaintext. 51 | location ~ /index\.php$ { 52 | include fastcgi_params; 53 | try_files $uri =404; 54 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 55 | fastcgi_pass unix:/var/run/php7-fpm.sock; 56 | fastcgi_index index.php; 57 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 58 | } 59 | 60 | location / { 61 | try_files $uri $uri/ /index.php?$query_string; 62 | } 63 | 64 | location = / { 65 | return 302 http://u.localhost; 66 | } 67 | 68 | location /uploads { 69 | internal; 70 | alias /path/to/pste/storage/app/uploads; 71 | } 72 | 73 | location ~ /robots.txt { 74 | allow all; 75 | log_not_found off; 76 | access_log off; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "devDependencies": { 4 | "gulp": "^3.8.8" 5 | }, 6 | "dependencies": { 7 | "laravel-elixir": "^5.0.0", 8 | "bootstrap-sass": "^3.0.0", 9 | "jquery": "1.12.2", 10 | "dropzone": "4.3.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | suites: 2 | main: 3 | namespace: App 4 | psr4_prefix: App 5 | src_path: app -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 |" + c + "
Your API key is:
3 |{{ Auth::user()->apikey }}4 |
This key allows anyone to upload to {{ config('upste.site_name') }} as you. Do not let anyone else see it.
5 | 11 |All API methods require your API key as a parameter named key
, either as a form field for POSTs or a GET parameter for GET requests.
Upload a file to your account and get a link to the file for sharing.
17 |Parameter | 21 |Description | 22 |Required | 23 |
---|---|---|
file | 28 |The file you wish to upload. | 29 |Yes | 30 |
Example
34 |curl \ 35 | -F key={{ Auth::user()->apikey }} \ 36 | -F file=@example.png \ 37 | {{ route('api.upload') }}38 | -------------------------------------------------------------------------------- /resources/views/account/faq.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.account') 2 | 3 | @section('title', 'FAQ') 4 | 5 | @section('content') 6 |
We strip EXIF data from uploaded images to prevent accidental breaches of user privacy from EXIF tags such as geolocation. This results in a different file size, and obviously a different hash.
14 |25 |28 |"Don't take the piss" much.
26 | 27 |
Email 47 | {{ config('upste.owner_email') }}{!! config('upste.owner_gpg') ? sprintf(' (GPG)', config('upste.owner_gpg')) : '' !!}. 48 |
49 |Please do not attempt to abuse bugs in the site's security for any purpose beyond reporting the bug as you will be instantly banned for life. Be responsible when other people's security is at risk. Don't be that guy. Nobody likes that guy.
50 |While only members can upload files, all uploads, as well as their original names, are visible to the public if they know (or accidentally 44 | find) the URL. Therefore, DO NOT upload anything you consider private as we will not accept any 45 | responsibility if it gets leaked. If you must upload private files, consider encrypting them first. 46 |
47 |tl;dr All uploads should be considered public.
48 |If you make something neat, let us know and we'll feature it 10 | here.
11 |You can upload to {{ config('upste.site_name') }} with our bash script.
13 |This script depends on slop and maim.
15 |Save this to ~/.config/pstepw
:
18 | #!/bin/bash 19 | 20 | # DO NOT SHARE YOUR KEY 21 | key={{ Auth::user()->apikey }} 22 | # Log URLs 23 | log=false 24 | # Log file location (if relevant) 25 | logfile="$HOME/.pstepw" 26 | # Copy links to clipboard after upload (requires xclip) 27 | clipboard=true 28 | # Send a notification when done 29 | notify=true 30 | # Open URL in browser 31 | browser=true 32 |33 |
Save this to any location in your $PATH
.
@include('account.resources.bash')38 |
Install Gentoo
8 |Name | 11 |Date | 13 |Actions | 14 ||
---|---|---|---|
{{ $user->name }} | 20 |{{ $user->email }} | 21 |{{ $user->created_at }} | 22 |
23 |
|
40 |
Total: {{ $user->getUploadsCount() }} ({{ App\Helpers::formatBytes($user->getUploadsSize()) }})
14 |Name | 11 |Registered | 13 |Uploads | 14 |Actions | 15 ||
---|---|---|---|---|
27 | {{ $user->name }} 29 | | 30 |{{ $user->email }} | 32 |{{ $user->created_at->copy()->tz(Auth::user()->preferences->timezone) }} | 33 |{{ $user->getUploadsCount() }} ({{ App\Helpers::formatBytes($user->getUploadsSize()) }}) | 34 |
35 |
|
65 |
{{ $exception->getMessage() ?: 'Page Not Found' }}
16 |{{ $exception->getMessage() ?: 'Down for maintenance, we\'ll be right back.' }}
15 |{{ config('upste.site_name') }} is a {{ config('upste.require_user_approval') ? 'private ' : '' }}file hosting website powered by uPste
9 | @if (config('upste.require_user_approval')) 10 | @if (config('upste.owner_name') && config('upste.owner_email')) 11 |Accounts are given with approval from {{ config('upste.owner_name') }} <{{ config('upste.owner_email') }}>.
13 | @endif 14 | @endif 15 | Login 16 | {{ config('upste.require_user_approval') ? 'Request' : 'Register' }} Account 17 |