Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Map uploads

  • Loading branch information...
commit bd3f85fa10408bbd813f8dd74f70100b22c77cf9 1 parent d6e2e59
@strager authored
View
1  .gitignore
@@ -3,3 +3,4 @@
/prod_tmp
/demo
/maps
+/server/maps
View
3  server/.htaccess
@@ -2,6 +2,9 @@ DirectorySlash On
Options FollowSymLinks Indexes
DirectoryIndex index.php
+# User upload is probably under 60MB
+php_value upload_max_filesize 60M
+
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -d
View
3  server/maps/.htaccess
@@ -0,0 +1,3 @@
+# Prevent maps from being evil
+Options -Indexes -ExecCGI
+php_flag engine off
View
2  server/src/config/default.inc.php
@@ -11,7 +11,7 @@
$config->forum_table_prefix = 'forum_';
$config->owp_script_path = APP_ROOT . '/../../owp.js'; // KINDA BROKEN FIXME
-$config->owp_maps_root = APP_ROOT . '/../../maps';
+$config->owp_maps_root = APP_ROOT . '/../maps';
$config->owp_skin_root = APP_ROOT . '/../../skin';
$config->ga_account_id = null;
View
4 server/src/lib/applicationfactory.php
@@ -43,4 +43,8 @@ function new_socialnetworks($c) {
$sn->twitter = $this->twitter;
return $sn;
}
+
+ function new_model_MapGateway($c) {
+ return new model_MapGateway($this->owp_maps_root, $c->create('PDO'));
+ }
}
View
2  server/src/lib/components/game/game.php
@@ -13,6 +13,8 @@ function map($name) {
switch ($name) {
case 'play':
return 'components_game_play';
+ case 'upload':
+ return 'components_game_upload';
}
}
View
49 server/src/lib/components/game/upload.php
@@ -0,0 +1,49 @@
+<?php
+
+class components_game_upload extends owpcomponent {
+ protected $templates;
+ protected $mapGateway;
+
+ function __construct(k_TemplateFactory $templates, model_MapGateway $mapGateway) {
+ $this->templates = $templates;
+ $this->mapGateway = $mapGateway;
+ }
+
+ function renderHTML() {
+ $this->document->setTitle('Upload map');
+
+ $t = $this->templates->create('upload-map');
+ return $t->render($this, array(
+ ));
+ }
+
+ function postMultipart() {
+ // Konstrukt's file access crap won't give us a fucking file handle,
+ // so we do things the old fashion way.
+ $osz = @$_FILES['osz'];
+ if ($osz) {
+ if ($osz['error']) {
+ return new k_HttpResponse(500, 'Error code ' . $osz['error']);
+ }
+
+ $maps = null;
+ $oszFilePath = $osz['tmp_name'];
+ try {
+ $maps = $this->mapGateway->saveOsz($oszFilePath);
+ } catch (Exception $e) {
+ // wtf finally
+ unlink($oszFilePath);
+ throw $e;
+ }
+
+ if (empty($maps)) {
+ return new k_HttpResponse(500);
+ }
+
+ // TODO Uploaded maps page
+ return new k_SeeOther($this->url(array('game', 'play'), $maps[0]->urlParams()));
+ } else {
+ return new k_HttpResponse(400, 'Bad request'); // Bad request
+ }
+ }
+}
View
144 server/src/lib/model/mapgateway.php
@@ -2,13 +2,13 @@
class model_MapGateway {
private static $mapFields = 'id, song_name, artist_name, difficulty_name, mapper_name, map_root, map_file, UNIX_TIMESTAMP(submit_date) AS submit_date';
+ //private static $osuFilenameRe = '/^((?<artist>.*) - (?<song>.*) \((?<mapper>.*)\) \\[(?<difficulty>.*)\\])\.osu$/i';
protected $mapsRoot;
protected $db;
- function __construct(owpjs $owpjs, PDO $db) {
- // FIXME MapGateway shouldn't depend upon owpjs
- $this->mapsRoot = $owpjs->mapsRoot;
+ function __construct($mapsRoot, PDO $db) {
+ $this->mapsRoot = $mapsRoot;
$this->db = $db;
}
@@ -35,6 +35,21 @@ function getMapFromParts($parts) {
return new model_Map($this, $row);
}
+ private function getMapsByIDs($ids) {
+ $quotedIDs = array();
+ foreach ($ids as $id) {
+ $quotedIDs[] = $this->db->quote((int) $id);
+ }
+
+ $result = $this->db->query('SELECT ' . self::$mapFields . ' FROM owp_maps WHERE id IN (' . implode(', ', $quotedIDs) . ')');
+
+ $maps = array();
+ while (($row = $result->fetch()) !== false) {
+ $maps[] = new model_Map($this, $row);
+ }
+ return $maps;
+ }
+
function getAllMaps() {
$statement = $this->db->prepare('SELECT ' . self::$mapFields . ' FROM owp_maps WHERE is_public = 1');
$statement->execute();
@@ -46,4 +61,127 @@ function getAllMaps() {
return $maps;
}
+
+ function saveOsz($oszFilePath) {
+ $zip = new ZipArchive();
+ if ($zip->open($oszFilePath) !== true) {
+ throw new Exception('Failed to open osz');
+ }
+
+ $mapDirectory = realpath($this->makeMapDirectory());
+
+ $safeFiles = array();
+ $osuFiles = array();
+ for ($i = 0; $i < $zip->numFiles; ++$i) {
+ $stat = $zip->statIndex($i);
+ $filename = $stat['name'];
+ $outPath = relativePath($mapDirectory, $mapDirectory . '/' . $filename);
+
+ if (preg_match(',^[\\\\\\/\\.],', $outPath)) {
+ // File entry is something like "foo/../../../../etc/passwd"
+ // or this is a dot file.
+ // Ignore for security reasons
+ } else {
+ if (preg_match('/\\.osu$/i', $filename)) {
+ $osuFiles[] = $filename;
+ }
+
+ $safeFiles[] = $filename;
+ }
+ }
+
+ if (empty($osuFiles)) {
+ throw new Exception('Not an osz file');
+ }
+
+ if (!$zip->extractTo($mapDirectory, $safeFiles)) {
+ throw new Exception('Failed to extract osz');
+ }
+
+ $zip->close();
+
+ $statement = $this->db->prepare('INSERT INTO owp_maps (song_name, artist_name, difficulty_name, mapper_name, map_root, map_file) VALUES (:song_name, :artist_name, :difficulty_name, :mapper_name, :map_root, :map_file)');
+
+ $mapIDs = array();
+ foreach ($osuFiles as $osuFile) {
+ // Hacky way to read an .osu file
+ // Example:
+ //
+ // Title:THANK YOU FOR PLAYING
+ // Artist:SUPER STAR -MITSURU-
+ // Creator:Natteke
+ // Version:Normal
+ // Source:Beatmania IIDX
+
+ $songName = $artistName = $mapperName = $difficultyName = '(null)';
+
+ $path = $mapDirectory . '/' . $osuFile;
+ $source = file_get_contents($path);
+
+ $matches = null;
+ if (preg_match('/^Title:(.*)$/m', $source, $matches)) {
+ $songName = trim($matches[1]);
+ }
+ if (preg_match('/^Artist:(.*)$/m', $source, $matches)) {
+ $artistName = trim($matches[1]);
+ }
+ if (preg_match('/^Version:(.*)$/m', $source, $matches)) {
+ $difficultyName = trim($matches[1]);
+ }
+ if (preg_match('/^Creator:(.*)$/m', $source, $matches)) {
+ $mapperName = trim($matches[1]);
+ }
+
+ $statement->bindValue('song_name', $songName);
+ $statement->bindValue('artist_name', $artistName);
+ $statement->bindValue('difficulty_name', $difficultyName);
+ $statement->bindValue('mapper_name', $mapperName);
+ $statement->bindValue('map_root', relativePath($this->mapsRoot, $mapDirectory));
+ $statement->bindValue('map_file', $osuFile);
+ $statement->execute();
+ $id = $this->db->lastInsertId();
+ $statement->closeCursor();
+
+ $mapIDs[] = $id;
+ }
+
+ return $this->getMapsByIDs($mapIDs);
+ }
+
+ function makeMapDirectory() {
+ $dirnameChars = 'abcdefghijklmnopqrstuvwxyz0123456789';
+ $length = 10;
+ $tries = 0;
+
+ if (!is_writable($this->mapsRoot)) {
+ throw new Exception('Map root not write-able');
+ }
+
+ while (true) {
+ ++$tries;
+
+ if ($tries % 10 === 0) {
+ // Every 10 tries, increase directory name length
+ ++$length;
+ }
+
+ if ($tries >= 50) {
+ throw new Exception('Exhausted');
+ }
+
+ // Random directory name
+ $dirname = substr(str_shuffle(str_repeat($dirnameChars, $length)), 0, $length);
+ $fullPath = $this->mapsRoot . '/' . $dirname;
+ if (file_exists($fullPath)) {
+ continue;
+ }
+
+ $success = mkdir($fullPath);
+ if (!$success) {
+ continue;
+ }
+
+ return $fullPath;
+ }
+ }
}
View
4 server/src/templates/maplist.tpl.php
@@ -1,8 +1,10 @@
<h1>owp maps</h1>
+<p><a href="<?php e(url(array('game', 'upload'))); ?>">Upload custom map</a></p>
+
<p>Choose a map to play:</p>
<ul>
<?php foreach ($maps as $map) { ?>
- <li><a href="<?php echo e(url(array('game', 'play'), $map->urlParams())); ?>"><?php echo e($map->title()); ?></a></li>
+ <li><a href="<?php e(url(array('game', 'play'), $map->urlParams())); ?>"><?php echo e($map->title()); ?></a></li>
<?php } ?>
</ul>
View
6 server/src/templates/upload-map.tpl.php
@@ -0,0 +1,6 @@
+<h1>Upload map</h1>
+
+<form method="POST" enctype="multipart/form-data">
+<p><label>Upload osu! <tt>.osz</tt> file:<br>
+<input type="file" required name="osz"></label> <input type="submit" value="Upload"></p>
+</form>
View
43 server/src/util.php
@@ -1,13 +1,50 @@
<?php
-// http://us2.php.net/manual/en/function.realpath.php#105876
+function normalizePath($path, $ps = DIRECTORY_SEPARATOR) {
+ $back = 0;
+ $stack = array();
+
+ foreach (explode($ps, $path) as $index => $part) {
+ switch ($part) {
+ case '':
+ if ($index === 0) {
+ // Initial /
+ array_push($stack, '');
+ } else {
+ // Double / or trailing /; ignore
+ }
+ break;
+
+ case '.':
+ // Ignore
+ break;
+
+ case '..':
+ if (empty($stack)) {
+ ++$back;
+ } else {
+ array_pop($stack);
+ }
+ break;
+
+ default:
+ array_push($stack, $part);
+ break;
+ }
+ }
+
+ return str_pad('', $back * 3, '..' . $ps) . implode($ps, $stack);
+}
+
+// Based upon http://us2.php.net/manual/en/function.realpath.php#105876
// Public domain
function relativePath($from, $to, $ps = DIRECTORY_SEPARATOR) {
+ $from = normalizePath($from, $ps);
+ $to = normalizePath($to, $ps);
$arFrom = explode($ps, rtrim($from, $ps));
$arTo = explode($ps, rtrim($to, $ps));
- while(count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0]))
- {
+ while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) {
array_shift($arFrom);
array_shift($arTo);
}
Please sign in to comment.
Something went wrong with that request. Please try again.